for example
// src/controllers/hello.controller.ts
import {HelloRepository} from '../repositories';
import {HelloMessage} from '../models';
import {get, param, HttpErrors} from '@loopback/rest';
import {repository} from '@loopback/repository';
export class HelloController {
constructor(@repository(HelloRepository) protected repo: HelloRepository) {}
// returns a list of our objects
@get('/messages')
async list(@param.query.number('limit') limit = 10): Promise<HelloMessage[]> {
// throw an error when the parameter is not a non-positive integer
if (!Number.isInteger(limit) || limit < 1) {
throw new HttpErrors.UnprocessableEntity('limit is non-positive');
} else if (limit > 100) {
limit = 100;
}
return await this.repo.find({limit});
}
}
invalid fields will result in a 422 unprocessable entity response with json body.
{
"message": "Validation Failed",
"errors": [
{
"resource": "Hello",
"field": "limit",
"code": "non_positive"
}
]
}
@hbakhtiyor Do you expect the error thrown by the controller code to override our built-in validation?
@raymondfeng I think he's asking after applying his own validation
if (!Number.isInteger(limit) || limit < 1) {
// how to custom the response here
}
how to custom the response like https://github.com/strongloop/loopback-next/pull/1753/files#diff-3497d60dccb2aa1d1fdf2955a7a080e9R239
I don't think the controller method is invoked at all as the validation of limit already fails before we route to the controller implementation.
@jannyHou yeah, @raymondfeng but the response matters, need in json and the structure
@hbakhtiyor Building HTTP error responses is a tricky business. It's easy to get it wrong and open your application to attacks.
In LoopBack (both 3.x and 4.x), we use our strong-error-handler middleware to take care of this. See Handling Errors in our docs.
Here are the important security constraints to keep in mind:
In production mode,
strong-error-handleromits details from error responses to prevent leaking sensitive information:
- For 5xx errors, the output contains only the status code and the status name from the HTTP specification.
- For 4xx errors, the output contains the full error message (
error.message) and the contents of thedetailsproperty (error.details) thatValidationErrortypically uses to provide machine-readable details about validation problems. It also includeserror.codeto allow a machine-readable error code to be passed through which could be used, for example, for translation.In debug mode,
strong-error-handlerreturns full error stack traces and internal details of any error objects to the client in the HTTP responses.
Now that I have warned you, LoopBack 4 makes it very easy to format the error messages your way. Just provide a custom implementation of the Sequence action reject. See Customizing Sequence Actions in our docs, it explain how to create a custom send action. The solution for reject is pretty much the same, you just need a different signature for the action function.
export class CustomRejectProvider implements Provider<Reject> {
// ...
action({request, response}: HandlerContext, error: Error) {
// handle the error and send back the error response
// "response" is an Express Response object
}
}
Caveat: some errors thrown by LB4 have only code set, these errors need a bit of pre-processing to decide what HTTP status code they should trigger. (For example, the error code ENTITY_NOT_FOUND should be mapped to the status code 404). The built-in reject action does not yet expose this pre-processing for consumption by custom reject actions. It's an oversight on our side, l created a new issue https://github.com/strongloop/loopback-next/issues/1942 to keep track of that.
@bajtos I have attempted making a custom error provider which takes two arguments: an array of errors and statusCode based on your comment. Here is the code I used:
export class CustomRejectProvider implements Provider<Reject> {
constructor(
@inject('customErrors') public errors: Array<Object>,
@inject('customErrorCode') public stathsCode: number,
) { }
value() {
// Use the lambda syntax to preserve the "this" scope for future calls!
console.log("test1");
return (response: HandlerContext, result: Error) => {
this.action(response, result);
};
}
action({ request, response }: HandlerContext, error: Error) {
// handle the error and send back the error response
// "response" is an Express Response object
console.log("test2");
if (error) {
console.log("test3");
error.message = 'Message: my error';
error.name = 'Name: some error';
const headers = (request.headers as any) || {};
const header = headers.accept || 'application/json';
response.setHeader('Content-Type', header);
response.sendStatus(401);
response.end(error);
} else {
console.log("test4");
response.end(response);
}
}
Response:
{
"errors": [{
"msg": "first element"
}, {
"msg": "second element"
}],
"statusCode": 401
}
The response however contains only the two injected variables (the error array and the statusCode) exactly as provided to the class without any processing. All the console log statements in my code have no effect at all. Any tips? I was also unable to modify the statusCode of the response. It's always 200 OK.
@shadyanwar
Replace
response.sendStatus(401);
response.end(error);
with
response.status(401).send(error);
and try, it worked for me.
Thanks @shadyanwar @pktippa. I've created PR based on the above suggestions. See https://github.com/strongloop/loopback-next/pull/4969/files.
@hbakhtiyor, we believe we've addressed your question. Closing as done. Please feel free to open a new issue if you find other problems. Thanks.