Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How can I catch errors by their status code? #382

Closed
dgreene1 opened this issue Jul 31, 2019 · 4 comments
Closed

How can I catch errors by their status code? #382

dgreene1 opened this issue Jul 31, 2019 · 4 comments

Comments

@dgreene1
Copy link
Collaborator

dgreene1 commented Jul 31, 2019

Figured out another thing that I'd like to share with the community.

Goal: You want to try/catch the results of your entire app, and if the error that's thrown has an http status code, then you want to return that to the user so they have more information. This is a common pattern due to the use of Boom

@dgreene1
Copy link
Collaborator Author

dgreene1 commented Jul 31, 2019

Here's how I wrote it:

import * as Router from 'koa-router';
import * as httpStatusCodes from 'http-status-codes';
import { AxiosError } from 'axios';
import * as createHttpError from 'http-errors';
import * as Boom from '@hapi/boom';
import { assertUnreachable } from '../agnosticUtilities/assertUnreachable';

function isAxiosError(input: unknown & object): input is AxiosError {
	const innocentUntilGuilty = input as Partial<AxiosError>;
	return !!innocentUntilGuilty.stack && !!innocentUntilGuilty.code && typeof innocentUntilGuilty.code === 'string';
}

function isHttpError(input: unknown & object): input is createHttpError.HttpError {
	const innocentUntilGuilty = input as Partial<createHttpError.HttpError>;
	return (
		!!innocentUntilGuilty.stack &&
		!!innocentUntilGuilty.statusCode &&
		typeof innocentUntilGuilty.statusCode === 'number'
	);
}

const grabStatusCodeFromError = (error: Error | AxiosError | createHttpError.HttpError | Boom): number | undefined => {
	// ######################
	// IMPORTANT NOTE: Boom must be first in the check since some libraries (like tsoa) automatically set the ".code" to 500 if there wasn't one.
	//      So if .code is the first thing you check, then you'd never arrive at .output.statusCode
	// ######################
	if (Boom.isBoom(error)) {
		return error.output.statusCode;
	}
	if (isHttpError(error)) {
		return error.statusCode;
	}
	if (isAxiosError(error)) {
		return error.code ? parseInt(error.code) : undefined;
	}
	if (error instanceof Error) {
		return undefined; // since standard errors don't have http status codes
	} else {
		return assertUnreachable(error, 'We got some kind of error that we do not have a handler for');
	}
};

export const errorResponder: Router.IMiddleware = async (ctx, next) => {
	try {
		await next();
	} catch (err) {
		const expose = process.env.EXPOSE_STACK;
		// tslint:disable-next-line: no-unsafe-any
		const statusCode = grabStatusCodeFromError(err) || httpStatusCodes.INTERNAL_SERVER_ERROR;

		ctx.status = statusCode;
		ctx.body = {
			// tslint:disable-next-line: no-unsafe-any
			message: err.message || 'An error occurred',
			// tslint:disable-next-line: no-unsafe-any
			correlationId: ctx.state.correlationId,
			// tslint:disable-next-line: no-unsafe-any
			stack: expose ? err.stack : undefined,
		};
	}
};

And then in your index.ts just remember to "install" the middleware:

app.use(errorResponder)

Note: just remember to add the middleware before the routes are added (as I describe in #381)

k, so here's how you would throw a 404 now that you have this middleware in place that supports Boom errors:

@Get('{id}')
public async getThingById(id: string, @Request() request: koa.Request): Promise<IThing> {
	const theThing = myDb.findById(id);
        if(!theThing){
        	throw Boom.notFound(`Could not find a resource by id ${id}`)
        }
        return theThing;
}

@ghost
Copy link

ghost commented Feb 18, 2020

What about with Express?

In express I get:
TypeError: Boom.badRequest is not a function

If I try to throw a Boom error after registering an express-boom handler with the server.

@dgreene1
Copy link
Collaborator Author

In express I get:
TypeError: Boom.badRequest is not a function

If I try to throw a Boom error after registering an express-boom handler with the server.

Please take that question to either StackOverflow or to the Boom github repo. It’s not tsoa related.

@ghost
Copy link

ghost commented Feb 18, 2020

ok np.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant