diff --git a/.componentsignore b/.componentsignore index f0418beb0..a3c6ebccf 100644 --- a/.componentsignore +++ b/.componentsignore @@ -1,5 +1,6 @@ [ "Adapter", + "BaseHttpError", "BasicConditions", "BasicRepresentation", "Error", diff --git a/src/util/Vocabularies.ts b/src/util/Vocabularies.ts index 3d902a8b4..e46a35cfc 100644 --- a/src/util/Vocabularies.ts +++ b/src/util/Vocabularies.ts @@ -141,6 +141,7 @@ export const SOLID = createUriAndTermNamespace('http://www.w3.org/ns/solid/terms ); export const SOLID_ERROR = createUriAndTermNamespace('urn:npm:solid:community-server:error:', + 'errorResponse', 'stack', ); diff --git a/src/util/errors/BadRequestHttpError.ts b/src/util/errors/BadRequestHttpError.ts index a2d93dfd7..df277d9de 100644 --- a/src/util/errors/BadRequestHttpError.ts +++ b/src/util/errors/BadRequestHttpError.ts @@ -1,24 +1,20 @@ import type { HttpErrorOptions } from './HttpError'; -import { HttpError } from './HttpError'; +import { generateHttpErrorClass } from './HttpError'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const BaseHttpError = generateHttpErrorClass(400, 'BadRequestHttpError'); /** * An error thrown when incoming data is not supported. * Probably because an {@link AsyncHandler} returns false on the canHandle call. */ -export class BadRequestHttpError extends HttpError { +export class BadRequestHttpError extends BaseHttpError { /** * Default message is 'The given input is not supported by the server configuration.'. * @param message - Optional, more specific, message. * @param options - Optional error options. */ public constructor(message?: string, options?: HttpErrorOptions) { - super(400, - 'BadRequestHttpError', - message ?? 'The given input is not supported by the server configuration.', - options); - } - - public static isInstance(error: any): error is BadRequestHttpError { - return HttpError.isInstance(error) && error.statusCode === 400; + super(message ?? 'The given input is not supported by the server configuration.', options); } } diff --git a/src/util/errors/ConflictHttpError.ts b/src/util/errors/ConflictHttpError.ts index bd0916b62..e7d18682a 100644 --- a/src/util/errors/ConflictHttpError.ts +++ b/src/util/errors/ConflictHttpError.ts @@ -1,14 +1,14 @@ import type { HttpErrorOptions } from './HttpError'; -import { HttpError } from './HttpError'; +import { generateHttpErrorClass } from './HttpError'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const BaseHttpError = generateHttpErrorClass(409, 'ConflictHttpError'); + /** * An error thrown when a request conflict with current state of the server. */ -export class ConflictHttpError extends HttpError { +export class ConflictHttpError extends BaseHttpError { public constructor(message?: string, options?: HttpErrorOptions) { - super(409, 'ConflictHttpError', message, options); - } - - public static isInstance(error: any): error is ConflictHttpError { - return HttpError.isInstance(error) && error.statusCode === 409; + super(message, options); } } diff --git a/src/util/errors/ForbiddenHttpError.ts b/src/util/errors/ForbiddenHttpError.ts index 2758cd15e..f272b3422 100644 --- a/src/util/errors/ForbiddenHttpError.ts +++ b/src/util/errors/ForbiddenHttpError.ts @@ -1,15 +1,14 @@ import type { HttpErrorOptions } from './HttpError'; -import { HttpError } from './HttpError'; +import { generateHttpErrorClass } from './HttpError'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const BaseHttpError = generateHttpErrorClass(403, 'ForbiddenHttpError'); /** * An error thrown when an agent is not allowed to access data. */ -export class ForbiddenHttpError extends HttpError { +export class ForbiddenHttpError extends BaseHttpError { public constructor(message?: string, options?: HttpErrorOptions) { - super(403, 'ForbiddenHttpError', message, options); - } - - public static isInstance(error: any): error is ForbiddenHttpError { - return HttpError.isInstance(error) && error.statusCode === 403; + super(message, options); } } diff --git a/src/util/errors/FoundHttpError.ts b/src/util/errors/FoundHttpError.ts index 9e33035d1..a88aef609 100644 --- a/src/util/errors/FoundHttpError.ts +++ b/src/util/errors/FoundHttpError.ts @@ -1,15 +1,14 @@ import type { HttpErrorOptions } from './HttpError'; -import { RedirectHttpError } from './RedirectHttpError'; +import { generateRedirectHttpErrorClass } from './RedirectHttpError'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const BaseHttpError = generateRedirectHttpErrorClass(302, 'FoundHttpError'); /** * Error used for resources that have been moved temporarily. */ -export class FoundHttpError extends RedirectHttpError { +export class FoundHttpError extends BaseHttpError { public constructor(location: string, message?: string, options?: HttpErrorOptions) { - super(302, location, 'FoundHttpError', message, options); - } - - public static isInstance(error: any): error is FoundHttpError { - return RedirectHttpError.isInstance(error) && error.statusCode === 302; + super(location, message, options); } } diff --git a/src/util/errors/HttpError.ts b/src/util/errors/HttpError.ts index 09ad058a5..2a69169ef 100644 --- a/src/util/errors/HttpError.ts +++ b/src/util/errors/HttpError.ts @@ -1,4 +1,9 @@ +import { DataFactory } from 'n3'; +import type { NamedNode, Quad, Quad_Subject } from 'rdf-js'; +import { toNamedTerm } from '../TermUtil'; +import { SOLID_ERROR } from '../Vocabularies'; import { isError } from './ErrorUtil'; +import quad = DataFactory.quad; export interface HttpErrorOptions { cause?: unknown; @@ -6,13 +11,19 @@ export interface HttpErrorOptions { details?: NodeJS.Dict; } +/** + * Returns a URI that is unique for the given status code. + */ +export function generateHttpErrorUri(statusCode: number): NamedNode { + return toNamedTerm(`${SOLID_ERROR.namespace}H${statusCode}`); +} + /** * 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 class HttpError extends Error implements HttpErrorOptions { - protected static readonly statusCode: number; - public readonly statusCode: number; +export class HttpError extends Error implements HttpErrorOptions { + public readonly statusCode: T; public readonly cause?: unknown; public readonly errorCode: string; public readonly details?: NodeJS.Dict; @@ -24,7 +35,7 @@ export class HttpError extends Error implements HttpErrorOptions { * @param message - Error message. * @param options - Optional options. */ - public constructor(statusCode: number, name: string, message?: string, options: HttpErrorOptions = {}) { + public constructor(statusCode: T, name: string, message?: string, options: HttpErrorOptions = {}) { super(message); this.statusCode = statusCode; this.name = name; @@ -36,4 +47,62 @@ export class HttpError extends Error implements HttpErrorOptions { public static isInstance(error: any): error is HttpError { return isError(error) && typeof (error as any).statusCode === 'number'; } + + /** + * Returns quads representing metadata relevant to this error. + */ + public generateMetadata(subject: Quad_Subject | string): Quad[] { + // The reason we have this here instead of the generate function below + // is because we still want errors created with `new HttpError` to be treated identical + // as errors created with the constructor of the error class corresponding to that specific status code. + return [ + quad(toNamedTerm(subject), SOLID_ERROR.terms.errorResponse, generateHttpErrorUri(this.statusCode)), + ]; + } +} + +/** + * Interface describing what an HttpError class should look like. + * This helps us make sure all HttpError classes have the same utility static functions. + */ +export interface HttpErrorClass { + new(message?: string, options?: HttpErrorOptions): HttpError; + + /** + * The status code corresponding to this error class. + */ + readonly statusCode: TCode; + /** + * A unique URI identifying this error class. + */ + readonly uri: NamedNode; + /** + * Checks if the given error is an instance of this class. + */ + readonly isInstance: (error: any) => error is HttpError; +} + +/** + * Generates a new HttpError class with the given status code and name. + * In general, status codes are used to uniquely identify error types, + * so there should be no 2 classes with the same value there. + * + * To make sure Components.js can work with these newly generated classes, + * the generated class should be called `BaseHttpError` as that name is an entry in `.componentsignore`. + * The actual class should then extend `BaseHttpError` and have a correct constructor, + * so the Components.js generator can generate the correct components JSON-LD file during build. + */ +export function generateHttpErrorClass(statusCode: TCode, name: string): HttpErrorClass { + return class SpecificHttpError extends HttpError { + public static readonly statusCode = statusCode; + public static readonly uri = generateHttpErrorUri(statusCode); + + public constructor(message?: string, options?: HttpErrorOptions) { + super(statusCode, name, message, options); + } + + public static isInstance(error: any): error is SpecificHttpError { + return HttpError.isInstance(error) && error.statusCode === statusCode; + } + }; } diff --git a/src/util/errors/InternalServerError.ts b/src/util/errors/InternalServerError.ts index c1418c9ab..7ff8a5789 100644 --- a/src/util/errors/InternalServerError.ts +++ b/src/util/errors/InternalServerError.ts @@ -1,14 +1,14 @@ import type { HttpErrorOptions } from './HttpError'; -import { HttpError } from './HttpError'; +import { generateHttpErrorClass } from './HttpError'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const BaseHttpError = generateHttpErrorClass(500, 'InternalServerError'); + /** * A generic error message, given when an unexpected condition was encountered and no more specific message is suitable. */ -export class InternalServerError extends HttpError { +export class InternalServerError extends BaseHttpError { public constructor(message?: string, options?: HttpErrorOptions) { - super(500, 'InternalServerError', message, options); - } - - public static isInstance(error: any): error is InternalServerError { - return HttpError.isInstance(error) && error.statusCode === 500; + super(message, options); } } diff --git a/src/util/errors/MovedPermanentlyHttpError.ts b/src/util/errors/MovedPermanentlyHttpError.ts index 70f88f243..1ae86f45a 100644 --- a/src/util/errors/MovedPermanentlyHttpError.ts +++ b/src/util/errors/MovedPermanentlyHttpError.ts @@ -1,15 +1,14 @@ import type { HttpErrorOptions } from './HttpError'; -import { RedirectHttpError } from './RedirectHttpError'; +import { generateRedirectHttpErrorClass } from './RedirectHttpError'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const BaseHttpError = generateRedirectHttpErrorClass(301, 'MovedPermanentlyHttpError'); /** * Error used for resources that have been moved permanently. */ -export class MovedPermanentlyHttpError extends RedirectHttpError { +export class MovedPermanentlyHttpError extends BaseHttpError { public constructor(location: string, message?: string, options?: HttpErrorOptions) { - super(301, location, 'MovedPermanentlyHttpError', message, options); - } - - public static isInstance(error: any): error is MovedPermanentlyHttpError { - return RedirectHttpError.isInstance(error) && error.statusCode === 301; + super(location, message, options); } } diff --git a/src/util/errors/NotFoundHttpError.ts b/src/util/errors/NotFoundHttpError.ts index df2dbb159..aed53e06f 100644 --- a/src/util/errors/NotFoundHttpError.ts +++ b/src/util/errors/NotFoundHttpError.ts @@ -1,14 +1,15 @@ import type { HttpErrorOptions } from './HttpError'; -import { HttpError } from './HttpError'; +import { generateHttpErrorClass } from './HttpError'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const BaseHttpError = generateHttpErrorClass(404, 'NotFoundHttpError'); + /** * An error thrown when no data was found for the requested identifier. */ -export class NotFoundHttpError extends HttpError { +export class NotFoundHttpError extends BaseHttpError { public constructor(message?: string, options?: HttpErrorOptions) { - super(404, 'NotFoundHttpError', message, options); - } - - public static isInstance(error: any): error is NotFoundHttpError { - return HttpError.isInstance(error) && error.statusCode === 404; + super(message, options); } } + diff --git a/src/util/errors/NotImplementedHttpError.ts b/src/util/errors/NotImplementedHttpError.ts index 059e6dab0..8404664e3 100644 --- a/src/util/errors/NotImplementedHttpError.ts +++ b/src/util/errors/NotImplementedHttpError.ts @@ -1,16 +1,15 @@ import type { HttpErrorOptions } from './HttpError'; -import { HttpError } from './HttpError'; +import { generateHttpErrorClass } from './HttpError'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const BaseHttpError = generateHttpErrorClass(501, 'NotImplementedHttpError'); /** * The server either does not recognize the request method, or it lacks the ability to fulfil the request. * Usually this implies future availability (e.g., a new feature of a web-service API). */ -export class NotImplementedHttpError extends HttpError { +export class NotImplementedHttpError extends BaseHttpError { public constructor(message?: string, options?: HttpErrorOptions) { - super(501, 'NotImplementedHttpError', message, options); - } - - public static isInstance(error: any): error is NotImplementedHttpError { - return HttpError.isInstance(error) && error.statusCode === 501; + super(message, options); } } diff --git a/src/util/errors/PayloadHttpError.ts b/src/util/errors/PayloadHttpError.ts index b8fad8b5f..bdff7c56d 100644 --- a/src/util/errors/PayloadHttpError.ts +++ b/src/util/errors/PayloadHttpError.ts @@ -1,23 +1,19 @@ import type { HttpErrorOptions } from './HttpError'; -import { HttpError } from './HttpError'; +import { generateHttpErrorClass } from './HttpError'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const BaseHttpError = generateHttpErrorClass(413, 'PayloadHttpError'); /** - * An error thrown when data exceeded the pre configured quota + * An error thrown when data exceeded the preconfigured quota */ -export class PayloadHttpError extends HttpError { +export class PayloadHttpError extends BaseHttpError { /** * Default message is 'Storage quota was exceeded.'. * @param message - Optional, more specific, message. * @param options - Optional error options. */ public constructor(message?: string, options?: HttpErrorOptions) { - super(413, - 'PayloadHttpError', - message ?? 'Storage quota was exceeded.', - options); - } - - public static isInstance(error: any): error is PayloadHttpError { - return HttpError.isInstance(error) && error.statusCode === 413; + super(message ?? 'Storage quota was exceeded.', options); } } diff --git a/src/util/errors/PreconditionFailedHttpError.ts b/src/util/errors/PreconditionFailedHttpError.ts index 3e63e4e86..d3c57e50f 100644 --- a/src/util/errors/PreconditionFailedHttpError.ts +++ b/src/util/errors/PreconditionFailedHttpError.ts @@ -1,15 +1,14 @@ import type { HttpErrorOptions } from './HttpError'; -import { HttpError } from './HttpError'; +import { generateHttpErrorClass } from './HttpError'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const BaseHttpError = generateHttpErrorClass(412, 'PreconditionFailedHttpError'); /** * An error thrown when access was denied due to the conditions on the request. */ -export class PreconditionFailedHttpError extends HttpError { +export class PreconditionFailedHttpError extends BaseHttpError { public constructor(message?: string, options?: HttpErrorOptions) { - super(412, 'PreconditionFailedHttpError', message, options); - } - - public static isInstance(error: any): error is PreconditionFailedHttpError { - return HttpError.isInstance(error) && error.statusCode === 412; + super(message, options); } } diff --git a/src/util/errors/RedirectHttpError.ts b/src/util/errors/RedirectHttpError.ts index d9012b774..2f23229d0 100644 --- a/src/util/errors/RedirectHttpError.ts +++ b/src/util/errors/RedirectHttpError.ts @@ -1,14 +1,14 @@ -import type { HttpErrorOptions } from './HttpError'; -import { HttpError } from './HttpError'; +import type { HttpErrorClass, HttpErrorOptions } from './HttpError'; +import { generateHttpErrorClass, HttpError } from './HttpError'; /** - * Abstract class representing a 3xx redirect. + * An error corresponding to a 3xx status code. + * Includes the location it redirects to. */ -export abstract class RedirectHttpError extends HttpError { +export class RedirectHttpError extends HttpError { public readonly location: string; - protected constructor(statusCode: number, location: string, name: string, message?: string, - options?: HttpErrorOptions) { + public constructor(statusCode: TCode, name: string, location: string, message?: string, options?: HttpErrorOptions) { super(statusCode, name, message, options); this.location = location; } @@ -17,3 +17,38 @@ export abstract class RedirectHttpError extends HttpError { return HttpError.isInstance(error) && typeof (error as any).location === 'string'; } } + +/** + * Interface describing what a {@link RedirectHttpError} class should look like. + * Makes sure a `location` value is always needed. + */ +export interface RedirectHttpErrorClass extends Omit, 'new'> { + new(location: string, message?: string, options?: HttpErrorOptions): RedirectHttpError; +} + +/** + * Generates a {@link RedirectHttpErrorClass}, similar to how {@link generateHttpErrorClass} works. + * The difference is that here a `location` field also gets set and the `getInstance` method + * also uses the {@link RedirectHttpError.isInstance} function. + */ +export function generateRedirectHttpErrorClass( + code: TCode, + name: string, +): RedirectHttpErrorClass { + // eslint-disable-next-line @typescript-eslint/naming-convention + const BaseClass = generateHttpErrorClass(code, name); + + // Need to extend `BaseClass` instead of `RedirectHttpError` to have the required static methods + return class SpecificRedirectHttpError extends BaseClass implements RedirectHttpError { + public readonly location: string; + + public constructor(location: string, message?: string, options?: HttpErrorOptions) { + super(message, options); + this.location = location; + } + + public static isInstance(error: any): error is SpecificRedirectHttpError { + return RedirectHttpError.isInstance(error) && error.statusCode === code; + } + }; +} diff --git a/src/util/errors/UnauthorizedHttpError.ts b/src/util/errors/UnauthorizedHttpError.ts index 3a3302c71..548c9c9f7 100644 --- a/src/util/errors/UnauthorizedHttpError.ts +++ b/src/util/errors/UnauthorizedHttpError.ts @@ -1,15 +1,14 @@ import type { HttpErrorOptions } from './HttpError'; -import { HttpError } from './HttpError'; +import { generateHttpErrorClass } from './HttpError'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const BaseHttpError = generateHttpErrorClass(401, 'UnauthorizedHttpError'); /** * An error thrown when an agent is not authorized. */ -export class UnauthorizedHttpError extends HttpError { +export class UnauthorizedHttpError extends BaseHttpError { public constructor(message?: string, options?: HttpErrorOptions) { - super(401, 'UnauthorizedHttpError', message, options); - } - - public static isInstance(error: any): error is UnauthorizedHttpError { - return HttpError.isInstance(error) && error.statusCode === 401; + super(message, options); } } diff --git a/src/util/errors/UnprocessableEntityHttpError.ts b/src/util/errors/UnprocessableEntityHttpError.ts index 1ff8c039a..4a9a45350 100644 --- a/src/util/errors/UnprocessableEntityHttpError.ts +++ b/src/util/errors/UnprocessableEntityHttpError.ts @@ -1,15 +1,14 @@ import type { HttpErrorOptions } from './HttpError'; -import { HttpError } from './HttpError'; +import { generateHttpErrorClass } from './HttpError'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const BaseHttpError = generateHttpErrorClass(422, 'UnprocessableEntityHttpError'); /** * An error thrown when the server understands the content-type but can't process the instructions. */ -export class UnprocessableEntityHttpError extends HttpError { +export class UnprocessableEntityHttpError extends BaseHttpError { public constructor(message?: string, options?: HttpErrorOptions) { - super(422, 'UnprocessableEntityHttpError', message, options); - } - - public static isInstance(error: any): error is UnprocessableEntityHttpError { - return HttpError.isInstance(error) && error.statusCode === 422; + super(message, options); } } diff --git a/src/util/errors/UnsupportedMediaTypeHttpError.ts b/src/util/errors/UnsupportedMediaTypeHttpError.ts index 7f86fe51e..e286cd887 100644 --- a/src/util/errors/UnsupportedMediaTypeHttpError.ts +++ b/src/util/errors/UnsupportedMediaTypeHttpError.ts @@ -1,15 +1,14 @@ import type { HttpErrorOptions } from './HttpError'; -import { HttpError } from './HttpError'; +import { generateHttpErrorClass } from './HttpError'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +const BaseHttpError = generateHttpErrorClass(415, 'UnsupportedMediaTypeHttpError'); /** * An error thrown when the media type of incoming data is not supported by a parser. */ -export class UnsupportedMediaTypeHttpError extends HttpError { +export class UnsupportedMediaTypeHttpError extends BaseHttpError { public constructor(message?: string, options?: HttpErrorOptions) { - super(415, 'UnsupportedMediaTypeHttpError', message, options); - } - - public static isInstance(error: any): error is UnsupportedMediaTypeHttpError { - return HttpError.isInstance(error) && error.statusCode === 415; + super(message, options); } } diff --git a/test/unit/util/errors/HttpError.test.ts b/test/unit/util/errors/HttpError.test.ts index 7847bfea4..8161f9457 100644 --- a/test/unit/util/errors/HttpError.test.ts +++ b/test/unit/util/errors/HttpError.test.ts @@ -1,8 +1,10 @@ +import 'jest-rdf'; +import { DataFactory } from 'n3'; import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpError'; import { ConflictHttpError } from '../../../../src/util/errors/ConflictHttpError'; import { ForbiddenHttpError } from '../../../../src/util/errors/ForbiddenHttpError'; -import type { HttpErrorOptions } from '../../../../src/util/errors/HttpError'; -import { HttpError } from '../../../../src/util/errors/HttpError'; +import { generateHttpErrorUri } from '../../../../src/util/errors/HttpError'; +import type { HttpErrorClass } from '../../../../src/util/errors/HttpError'; import { InternalServerError } from '../../../../src/util/errors/InternalServerError'; import { MethodNotAllowedHttpError } from '../../../../src/util/errors/MethodNotAllowedHttpError'; import { NotFoundHttpError } from '../../../../src/util/errors/NotFoundHttpError'; @@ -12,16 +14,11 @@ import { PreconditionFailedHttpError } from '../../../../src/util/errors/Precond import { UnauthorizedHttpError } from '../../../../src/util/errors/UnauthorizedHttpError'; import { UnprocessableEntityHttpError } from '../../../../src/util/errors/UnprocessableEntityHttpError'; import { UnsupportedMediaTypeHttpError } from '../../../../src/util/errors/UnsupportedMediaTypeHttpError'; - -// Only used to make typings easier in the tests -class FixedHttpError extends HttpError { - public constructor(message?: string, options?: HttpErrorOptions) { - super(0, '', message, options); - } -} +import { SOLID_ERROR } from '../../../../src/util/Vocabularies'; +const { literal, namedNode, quad } = DataFactory; describe('HttpError', (): void => { - const errors: [string, number, typeof FixedHttpError][] = [ + const errors: [string, number, HttpErrorClass][] = [ [ 'BadRequestHttpError', 400, BadRequestHttpError ], [ 'UnauthorizedHttpError', 401, UnauthorizedHttpError ], [ 'ForbiddenHttpError', 403, ForbiddenHttpError ], @@ -48,6 +45,10 @@ describe('HttpError', (): void => { expect(constructor.isInstance(instance)).toBeTruthy(); }); + it('has a URI.', (): void => { + expect(constructor.uri).toEqualRdfTerm(generateHttpErrorUri(statusCode)); + }); + it(`has name ${name}.`, (): void => { expect(instance.name).toBe(name); }); @@ -75,5 +76,12 @@ describe('HttpError', (): void => { it('sets the details.', (): void => { expect(instance.details).toBe(options.details); }); + + it('generates metadata.', (): void => { + const subject = namedNode('subject'); + expect(instance.generateMetadata(subject)).toBeRdfIsomorphic([ + quad(subject, SOLID_ERROR.terms.errorResponse, constructor.uri), + ]); + }); }); }); diff --git a/test/unit/util/errors/RedirectHttpError.test.ts b/test/unit/util/errors/RedirectHttpError.test.ts index 5536c86fc..6a2a106bd 100644 --- a/test/unit/util/errors/RedirectHttpError.test.ts +++ b/test/unit/util/errors/RedirectHttpError.test.ts @@ -1,16 +1,23 @@ import { FoundHttpError } from '../../../../src/util/errors/FoundHttpError'; import type { HttpErrorOptions } from '../../../../src/util/errors/HttpError'; +import { generateHttpErrorUri } from '../../../../src/util/errors/HttpError'; import { MovedPermanentlyHttpError } from '../../../../src/util/errors/MovedPermanentlyHttpError'; import { RedirectHttpError } from '../../../../src/util/errors/RedirectHttpError'; +import type { RedirectHttpErrorClass } from '../../../../src/util/errors/RedirectHttpError'; +// Used to make sure the RedirectHttpError constructor also gets called in a test. class FixedRedirectHttpError extends RedirectHttpError { + public static readonly statusCode = 0; + public static readonly uri = generateHttpErrorUri(0); + public constructor(location: string, message?: string, options?: HttpErrorOptions) { - super(0, location, '', message, options); + super(0, 'RedirectHttpError', location, message, options); } } describe('RedirectHttpError', (): void => { - const errors: [string, number, typeof FixedRedirectHttpError][] = [ + const errors: [string, number, RedirectHttpErrorClass][] = [ + [ 'RedirectHttpError', 0, FixedRedirectHttpError ], [ 'MovedPermanentlyHttpError', 301, MovedPermanentlyHttpError ], [ 'FoundHttpError', 302, FoundHttpError ], ];