Fastify error handler returns wrong error if I call reply.code() before returning the error object - fastify

I have an async error handler that looks like this
export default async function processError(
err: FastifyError,
req: FastifyRequest,
res: FastifyReply
): Promise<FastifyError | TErrorReply> {
console.log({ err: err.stack });
let processedError: TErrorReply = err;
if (isDatabaseError(err))
processedError = processDatabaseError(req, res, err);
if (isSchemaValidationError(err))
processedError = processSchemaValidationError(req, res, err);
if (process.env.NODE_ENV === 'development')
processedError = {
...processedError,
stack: err.stack || new Error().stack,
};
console.log({ processedError });
res.code(Number(err?.code || err?.statusCode || 500));
return processedError;
}
I then use it like this
fastify.setErrorHandler(processError);
Inside processSomethingError functions I set a specific code and return a specific error, here is an example.
function processDatabaseError(
req: FastifyRequest,
res: FastifyReply,
err: DatabaseError
): TErrorReply {
const t = getFixedT('en', 'errors');
switch (err.code) {
case '23505': {
const match = err.detail?.match(/\((.+?)\)/);
if (match) {
const key = match[1];
res.code(409);
return {
fields: { [key]: 'duplicated' },
};
}
}
}
res.code(400);
return {
message: t<string, TTranslationKeys['errors']>('fallback'),
};
}
My problem is that if I have
res.code(Number(err?.code || err?.statusCode || 500));
inside processError, just before returning the processedError, it ignores the processedError and instead sends the original error, which is represented by the err argument inside processError function.
If I remove the res.code.. - everything works fine and the actual processedError is returned.
Why is it working like this?
I just want to set the code or the error I want to return, so in headers it shows the right status code.

It turned out that err?.code is a string, so it would throw an error inside res.code function since it wants a number.

Related

Absence of the required field does not cause an error. typescript

In my nodejs project i have a controller
const addQuestion = async(req : Request, res : Response) => {
try {
const question: IQuestion = req.body;
if (!(await checkExist(question))) {
return res.status(409).send();
}
redisAsync.set(question.question, question.answer);
return res.status(200).send();
}
catch (err) {
return res.status(422).send();
}
}
And an interface
interface IQuestion {
question : string;
answer : string;
}
So, I expect to catch an error, when I pass to the controller JSON without question and/or answer fields, then give 422 code, but it not happens and code executes without required fields in object
While TypeScript is statically typed, it still executes as JavaScript that's dynamic. TypeScript doesn't check types at runtime. Therefore when you cast any value to an IQuestion, TypeScript expects you to make sure it is an IQuestion.
Basically, you need to manually verify the fields. A type guard / type predicate is ideal for this:
function isQuestion(question: any): question is IQuestion {
return typeof question === 'object'
&& typeof question.question === 'string'
&& typeof question.answer === 'string';
}
const addQuestion = async (req: Request, res: Response) => {
try {
const question: IQuestion = req.body;
if (!isQuestion(question)) {
return res.status(422).json({ error: 'Expected an IQuestion as body' });
}
if (!(await checkExist(question))) {
return res.status(409).send();
}
redisAsync.set(question.question, question.answer);
return res.status(200).send();
}
catch (err) {
return res.status(422).send();
}
}

Discord.js - Cannot edit a message

I have a problem. Im trying to edit a message and I'm getting an error. How can i fix this?
TypeError: polje.edit is not a function
My code :
if (msg === "test") {
let polje = message.channel.send(poljeprazno);
try {
var odgovor = await message.channel.awaitMessages(message2 => message2.content === "a1", {
maxMatches: 1,
time: 5000,
errors: ['time']
});
if (odgovor.first().content === "a1") {
if (poljeprazno[0][0] != "⬜") return message.channel.send("Zasedeno");
poljeprazno[0][0] = "⭕";
polje.edit(poljeprazno);
}
}catch (err) {
console.error(err);
return message.channel.send("Ničesar nisem dobil").then(d_msg => { d_msg.delete(5000); });
}
}
You forgot to use await when sending the message, and so polje is a Promise<Message>, instead of an actual Message. You can just write it like this:
let polje = await message.channel.send(poljeprazno)
Consider this:
let polje = message.channel.send(poljeprazno);
message.channel.send() returns a Promise. It's an asynchronous function, so there's no guarantee that when you call polje.edit(poljeprazno); that poleje even has returned the Message object in which the edit() function lives.
You want:
let polje = await message.channel.send(poljeprazno);

How to assert the properties of a class inside a mock function with jest

I'm having issues with mocked functions that are passing a class as a parameter. For some reason, it doesn't seem to assert the content correctly
// ErrorHandler.js
class ErrorHandler extends Error {
constructor(status, reason) {
super();
this.status = status;
this.reason = reason;
}
}
export {
ErrorHandler
}
// express file express.js
const method = async (req, res, next) => {
try {
throw new ErrorHandler(401, 'error')
next()
} catch (error) {
next(error)
}
}
// test file
it('should call the next method with the proper error', async () => {
const request = {
body: {}
}
const next = jest.fn()
const response = mockResponse() // here it's just a simple mock
await method(request, response, next)
expect(next).toHaveBeenCalledWith(
// here the problem is that it doesn't seem to assert the parameters
// and this test is passing
new ErrorHandler('random text')
)
})
I tried mocking the ErrorHandler class but then It gives another error related that it can't compare the next method anymore
The problem is that Jest is trying to compare two error objects and doesn't really know how. You can see this with a simple assertion:
expect(new ErrorHandler(404, 'not found')).not.toEqual(new ErrorHandler(401, 'unauthorized'))
with the result:
expect(received).not.toEqual(expected) // deep equality
Expected: not [Error]
You need to be more specific, e.g.:
expect(next).toHaveBeenCalled();
const [err] = next.mock.calls[0];
expect(err).toMatchObject({ status: 401, reason: 'error' });

Nodejs Async/Await in api controller responds with unknown error

export function getAllHost(req, res) {
function findAllHost() {
let query = {};
query.company = req.query && req.query.companyId ? req.query.companyId : res.locals.payload.companyId;
query.active = true;
if(req.query && req.query.name) {
let regexp = new RegExp('\\b' + req.query.name);
query['name'] = {$regex: regexp, $options: 'i'};
}
return Host.find(query);
}
async function sendRes() {
let allHost = [];
let hosts = [];
try {
hosts = await findAllHost();
} catch(err){
console.log(err)
}
for (let host of hosts) {
allHost.push(new Host_V1(host));
}
return allHost
}
sendRes().then(data => res.status(200).json(data)).catch(error => {
console.log("error is", error);
res.status(error.status || 500).json({
message: error.status ? error.message : 'Server Error'
});
});
}
I have been trying to adapt async/await into my code, so I converted one of the Promise based api controller to make use of async/await, but the thing that bothers me is that my server responds with 500, and the console.log inside my catch block doesn't print anything.
No error gets thrown.
I am using babel babel-plugin-syntax-async-functions to parse it.
What is it that I am doing wrong?
Your code is a bit overcomplicated, but judging by it you should receive an error in the console if one appears. It could instead be that you have a middleware producing an error? The main issue is that you're catching the error in the async function sendRes, so the .catch-method you call on the returned Promise will never be fired even if there is an error.
Like many others that are new to async/await, you've misunderstood and believe that you have to wrap every await expression in a try/catch-block. This is not the case. The error "trickles" up the call chain, and unless a particular function can provide a different return value, it's best to catch the error from the top-most callee. Take this simple example which shows a common anti-pattern: https://repl.it/repls/PunySafeInterfacestandard (await and async isn't even needed in these examples, but I added them for clarity)
But if you try to simplify your code, maybe something like the below snippet, you might be able to rule out if it's a middleware or not.
export async function getAllHosts (req, res) {
try {
let query = {
company: req.query && req.query.companyId ? req.query.companyId : res.locals.payload.companyId,
active: true
}
if (req.query && req.query.name) {
let regexp = new RegExp('\\b' + req.query.name)
query.name = {$regex: regexp, $options: 'i'}
}
let allHosts = (await Host.find(query)).map((host) => new Host_V1(host))
return res.json(allHosts)
} catch (e) {
console.log("error is", e)
res.status(error.status || 500).json({
message: error.status ? error.message : 'Server Error'
})
}
}
Take note of the (await Host.find(query)) line. If the Promise that's returned from Host.find() rejects, the .map()-method won't be executed on the data and execution will jump to the catch-block.
I also heavily discourage using Babel since async/await has been natively supported in Node since version 7.6.
EDIT : The value returned is indeed a promise
I think that the error is related to your 'SendRes.then' since what SendRes returns is not a promise but it's actually is the Array allHost
Your data argument is not an argument anymore it is the value returned from the sendRes function thanks to your async / await implementation.
const hosts = sendRes();
res.status(200).json(hosts);
if you want to handle an error you must return something from your catch block so that you can can handle it here.
if (!hosts) res.status(500);
To simplify your code you can also get rid of the sendRes function and make your getAllHost express middleware an async function
async getAllHost(req, res) { /*...*/ }
try {
let allHosts = await findAllHosts();
res.status(200).json(allHosts);
} catch (error) {
res.status(500).send({ error })
}

Correct way of handling promisses and server response

I am trying to improve my code in node.js / sail.js and I am fighting server response in promisses.
When you look at the first .then function you can see that method returns false in case of forbidden access or notFound. Then, in the next .then functions I must check if the return type is === false to skip to section and avoid sending http headers twice. Can this be improved somehow, to skip all next .then methods in case of failure? I can throw an Exception to go in the last .catch but then there must be a case to switch between all possible states. (i.e. forbidden, serverError or even not found)
Notification.findOne({id: req.param('id')})
.then(function(notification) {
if (!notification) {
res.notFound();
return false;
}
if (notification.triggeredBy != req.session.user.id) {
res.forbidden();
return false;
}
return notification;
})
.then(function(notification) {
if (notification === false) {
return false;
}
return Notification.update(notification.id, actionUtil.parseValues(req));
})
.then(function(notification) {
if (notification === false) {
return false;
}
res.json(notification);
})
.catch(function(err) {
sails.log(err);
res.serverError({message: 'A server error occurred.'});
})
If I would do this, first I seperate logic and receving/sending function. Second I specify listing of error codes. And it will be like that:
NotificationService.js
/*
Listing of error codes: {
* [1] Object not found
* [2] Forbidden
* [3] Server error
}
*/
module.exports = {
nameOfMethod: function(ID, sessionID) {
return new Promise(function(resolve, reject) {
Notification.findOne({ id: ID })
.then(function(notification) {
if (!notification) return reject({ error_code: 1 });
if (notification.triggeredBy !== sessionID) return reject({ error_code: 2 });
Notification.update(notification.id, actionUtil.parseValues(req))
.then(function(notification) {
return resolve(notification); // finally return our notification
})
.catch(function(err) {
sails.log.error(err); // It's good when log is classified. In this case is error
return reject({ message: 'A server error occurred.' });
});
})
.catch(function(err) {
sails.log.error(err);
return reject({ message: 'A server error occurred.' });
});
});
}
};
NotificationController.js
module.exports = {
notifyMe: function(req, res) {
const ID = req.param('id'), sessionID = req.session.user.id;
NotificationService.nameOfMethod(ID, sessionID)
.then(function(notification) {
return res.send(notification);
})
.catch(function(err) {
switch (err.error_code) {
case 1:
return res.notFound(err);
case 2:
return res.forbidden(err);
default:
return res.serverError(err);
}
});
}
};
In case where I use switch I think it is better way to select right response but on this time I haven't any idea
See how filtered .catch() is implemented in Bluebird - it can be useful in your case to throw all errors you need but avoid having a big switch/case block in the catch handler:
.catch(
class ErrorClass|function(any error)|Object predicate...,
function(any error) handler
) -> Promise
.caught(
class ErrorClass|function(any error)|Object predicate...,
function(any error) handler
) -> Promise
This is an extension to .catch to work more like catch-clauses in
languages like Java or C#. Instead of manually checking instanceof or
.name === "SomeError", you may specify a number of error constructors
which are eligible for this catch handler. The catch handler that is
first met that has eligible constructors specified, is the one that
will be called.
Example:
somePromise.then(function() {
return a.b.c.d();
}).catch(TypeError, function(e) {
//If it is a TypeError, will end up here because
//it is a type error to reference property of undefined
}).catch(ReferenceError, function(e) {
//Will end up here if a was never declared at all
}).catch(function(e) {
//Generic catch-the rest, error wasn't TypeError nor
//ReferenceError
});
See: http://bluebirdjs.com/docs/api/catch.html#filtered-catch
Instead of:
return false;
you can use:
return Promise.reject(someReason);
or:
throw someReason;
and you won't have to check for those false values - just use (possibly multiple) catch handlers.

Resources