Skip to content

Commit

Permalink
fix: Retain status codes when combining errors
Browse files Browse the repository at this point in the history
  • Loading branch information
joachimvh committed Sep 30, 2020
1 parent d8e6c08 commit de275dc
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 4 deletions.
30 changes: 29 additions & 1 deletion src/util/CompositeAsyncHandler.ts
@@ -1,4 +1,5 @@
import type { AsyncHandler } from './AsyncHandler';
import { HttpError } from './errors/HttpError';
import { UnsupportedHttpError } from './errors/UnsupportedHttpError';

/**
Expand Down Expand Up @@ -86,8 +87,35 @@ export class CompositeAsyncHandler<TIn, TOut> implements AsyncHandler<TIn, TOut>
}
}

let differentCodes = false;
const last = { statusCode: 0, name: '' };

for (const error of errors) {
if (error instanceof HttpError) {
if (last.statusCode !== error.statusCode) {
if (last.statusCode > 0) {
// Don't break yet since there might still be a 500 error in there
differentCodes = true;
} else {
last.statusCode = error.statusCode;
last.name = error.name;
}
}
} else {
last.statusCode = 500;
break;
}
}

const joined = errors.map((error: Error): string => error.message).join(', ');
const message = `No handler supports the given input: [${joined}].`;

throw new UnsupportedHttpError(`No handler supports the given input: [${joined}].`);
if (last.statusCode === 500) {
throw new Error(message);
}
if (differentCodes) {
throw new UnsupportedHttpError(message);
}
throw new HttpError(last.statusCode, last.name, message);
}
}
6 changes: 3 additions & 3 deletions src/util/errors/HttpError.ts
@@ -1,8 +1,8 @@
/**
* An abstract class for all errors that could be thrown by Solid.
* A class for all errors that could be thrown by Solid.
* All errors inheriting from this should fix the status code thereby hiding the HTTP internals from other components.
*/
export abstract class HttpError extends Error {
export class HttpError extends Error {
public statusCode: number;

/**
Expand All @@ -11,7 +11,7 @@ export abstract class HttpError extends Error {
* @param name - Error name. Useful for logging and stack tracing.
* @param message - Message to be thrown.
*/
protected constructor(statusCode: number, name: string, message?: string) {
public constructor(statusCode: number, name: string, message?: string) {
super(message);
this.statusCode = statusCode;
this.name = name;
Expand Down
42 changes: 42 additions & 0 deletions test/unit/util/CompositeAsyncHandler.test.ts
@@ -1,5 +1,7 @@
import type { AsyncHandler } from '../../../src/util/AsyncHandler';
import { CompositeAsyncHandler } from '../../../src/util/CompositeAsyncHandler';
import { HttpError } from '../../../src/util/errors/HttpError';
import { UnsupportedHttpError } from '../../../src/util/errors/UnsupportedHttpError';
import { StaticAsyncHandler } from '../../util/StaticAsyncHandler';

describe('A CompositeAsyncHandler', (): void => {
Expand Down Expand Up @@ -81,5 +83,45 @@ describe('A CompositeAsyncHandler', (): void => {

await expect(handler.handleSafe(null)).rejects.toThrow('[Not supported., Not supported.]');
});

it('throws an error with matching status code if all handlers threw the same.', async(): Promise<void> => {
handlerTrue.canHandle = async(): Promise<void> => {
throw new HttpError(401, 'UnauthorizedHttpError');
};
const handler = new CompositeAsyncHandler([ handlerTrue, handlerTrue ]);

await expect(handler.canHandle(null)).rejects.toMatchObject({
statusCode: 401,
name: 'UnauthorizedHttpError',
});
});

it('throws a standard error if one of the handlers threw one.', async(): Promise<void> => {
handlerTrue.canHandle = async(): Promise<void> => {
throw new HttpError(401, 'UnauthorizedHttpError');
};
handlerFalse.canHandle = async(): Promise<void> => {
throw new Error('Server is crashing!');
};
const handler = new CompositeAsyncHandler([ handlerTrue, handlerFalse ]);

try {
await handler.canHandle(null);
} catch (error: unknown) {
expect((error as any).statusCode).toBeUndefined();
}
});

it('throws an UnsupportedHttpError if handlers throw different errors.', async(): Promise<void> => {
handlerTrue.canHandle = async(): Promise<void> => {
throw new HttpError(401, 'UnauthorizedHttpError');
};
handlerFalse.canHandle = async(): Promise<void> => {
throw new HttpError(415, 'UnsupportedMediaTypeHttpError');
};
const handler = new CompositeAsyncHandler([ handlerTrue, handlerFalse ]);

await expect(handler.canHandle(null)).rejects.toThrow(UnsupportedHttpError);
});
});
});

0 comments on commit de275dc

Please sign in to comment.