Skip to content

Commit 1dbfbea

Browse files
committed
Added field HttpError.data containing full JSON response
1 parent 6700cb1 commit 1dbfbea

File tree

4 files changed

+61
-68
lines changed

4 files changed

+61
-68
lines changed

endpoints/Endpoint.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ test('errorHandlingWithNoContent', async () => {
242242

243243
test('errorHandlingWithMessage', async () => {
244244
fetchMock.mockOnceIf('http://localhost/endpoint',
245-
'{"message":"my message"}',
245+
'{"message":"my message","extra":"info"}',
246246
{
247247
status: HttpStatusCode.Conflict,
248248
headers: {
@@ -254,7 +254,9 @@ test('errorHandlingWithMessage', async () => {
254254
try {
255255
await endpoint.get();
256256
} catch (err) {
257-
errorThrown = err instanceof ConflictError && err.message === 'my message';
257+
errorThrown = err instanceof ConflictError
258+
&& err.message === 'my message'
259+
&& err.data.extra === 'info';
258260
}
259261
expect(errorThrown).toBe(true);
260262
});

errors/DefaultErrorHandler.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ErrorHandler, HttpError, BadRequestError, AuthenticationError, AuthorizationError, NotFoundError, TimeoutError, ConflictError, ConcurrencyError, RangeError } from ".";
1+
import { ErrorHandler, HttpError, BadRequestError, AuthenticationError, AuthorizationError, NotFoundError, TimeoutError, ConflictError, ConcurrencyError } from ".";
22
import { HttpStatusCode, HttpHeader } from "../http";
33

44
/**
@@ -15,46 +15,42 @@ export class DefaultErrorHandler implements ErrorHandler {
1515
* @throws {@link TimeoutError}: {@link HttpStatusCode.RequestTimeout}
1616
* @throws {@link ConflictError}: {@link HttpStatusCode.Conflict}
1717
* @throws {@link ConcurrencyError}: {@link HttpStatusCode.PreconditionFailed}
18-
* @throws {@link RangeError}: {@link HttpStatusCode.RequestedRangeNotSatisfiable}
1918
* @throws {@link HttpError}: Other non-success status code
2019
*/
2120
async handle(response: Response) {
2221
if (response.ok) return;
2322

24-
const message = await this.extractJsonMessage(response);
25-
throw DefaultErrorHandler.mapToError(
26-
response.status,
27-
message ?? `HTTP ${response.status} ${response.statusText}`);
28-
}
29-
30-
private async extractJsonMessage(response: Response) {
3123
const contentType = response.headers.get(HttpHeader.ContentType);
32-
return (contentType?.startsWith("application/json") || contentType?.includes("+json"))
33-
? (await response.json())?.message
24+
const jsonBody = (contentType?.startsWith("application/json") || contentType?.includes("+json"))
25+
? await response.json()
3426
: undefined;
27+
28+
const errorType = DefaultErrorHandler.errorType(response.status);
29+
throw new errorType(
30+
jsonBody?.message ?? jsonBody?.details ?? `HTTP ${response.status} ${response.statusText}`,
31+
response.status,
32+
jsonBody);
3533
}
3634

37-
private static mapToError(status: HttpStatusCode, message: string) {
35+
private static errorType(status: HttpStatusCode): new (message: string, status: HttpStatusCode, data?: any) => Error {
3836
switch (status) {
3937
case HttpStatusCode.BadRequest:
40-
return new BadRequestError(message, status)
38+
return BadRequestError;
4139
case HttpStatusCode.Unauthorized:
42-
return new AuthenticationError(message, status)
40+
return AuthenticationError;
4341
case HttpStatusCode.Forbidden:
44-
return new AuthorizationError(message, status)
42+
return AuthorizationError;
4543
case HttpStatusCode.NotFound:
4644
case HttpStatusCode.Gone:
47-
return new NotFoundError(message, status)
45+
return NotFoundError;
4846
case HttpStatusCode.RequestTimeout:
49-
return new TimeoutError(message, status)
47+
return TimeoutError;
5048
case HttpStatusCode.Conflict:
51-
return new ConflictError(message, status)
49+
return ConflictError;
5250
case HttpStatusCode.PreconditionFailed:
53-
return new ConcurrencyError(message, status)
54-
case HttpStatusCode.RequestedRangeNotSatisfiable:
55-
return new RangeError(message, status)
51+
return ConcurrencyError;
5652
default:
57-
return new HttpError(message, status);
53+
return HttpError;
5854
}
5955
}
6056
}

errors/Errors.ts

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,56 +2,52 @@ import { HttpStatusCode } from "../http";
22

33
/* tslint:disable:max-classes-per-file */
44

5+
/**
6+
* Thrown on HTTP response with a non-successful status code (4xx or 5xx).
7+
*/
58
export class HttpError extends Error {
6-
constructor(message: string, public status: HttpStatusCode) {
9+
/**
10+
* Creates a new HTTP error.
11+
* @param message The error message.
12+
* @param status The HTTP status code.
13+
* @param data Additional error data.
14+
*/
15+
constructor(message: string, public status: HttpStatusCode, public data?: any) {
716
super(message);
817
}
918
}
1019

11-
export class BadRequestError extends HttpError {
12-
constructor(message: string, status = HttpStatusCode.BadRequest) {
13-
super(message, status);
14-
}
15-
}
20+
/**
21+
* Thrown on HTTP response for a bad request (usually {@link HttpStatusCode.BadRequest}).
22+
*/
23+
export class BadRequestError extends HttpError {}
1624

17-
export class AuthenticationError extends HttpError {
18-
constructor(message: string, status = HttpStatusCode.Unauthorized) {
19-
super(message, status);
20-
}
21-
}
25+
/**
26+
* Thrown on HTTP response for an unauthenticated request, i.e. missing credentials (usually {@link HttpStatusCode.Unauthorized}).
27+
*/
28+
export class AuthenticationError extends HttpError {}
2229

23-
export class AuthorizationError extends HttpError {
24-
constructor(message: string, status = HttpStatusCode.Forbidden) {
25-
super(message, status);
26-
}
27-
}
30+
/**
31+
* Thrown on HTTP response for an unauthorized request, i.e. missing permissions (usually {@link HttpStatusCode.Forbidden}).
32+
*/
33+
export class AuthorizationError extends HttpError {}
2834

29-
export class NotFoundError extends HttpError {
30-
constructor(message: string, status = HttpStatusCode.NotFound) {
31-
super(message, status);
32-
}
33-
}
35+
/**
36+
* Thrown on HTTP response for a missing resource (usually {@link HttpStatusCode.NotFound} or {@link HttpStatusCode.Gone}).
37+
*/
38+
export class NotFoundError extends HttpError {}
3439

35-
export class TimeoutError extends HttpError {
36-
constructor(message: string, status = HttpStatusCode.RequestTimeout) {
37-
super(message, status);
38-
}
39-
}
40-
41-
export class ConflictError extends HttpError {
42-
constructor(message: string, status = HttpStatusCode.Conflict) {
43-
super(message, status);
44-
}
45-
}
40+
/**
41+
* Thrown on HTTP response for a timed-out operation (usually {@link HttpStatusCode.Timeout}).
42+
*/
43+
export class TimeoutError extends HttpError {}
4644

47-
export class ConcurrencyError extends HttpError {
48-
constructor(message: string, status = HttpStatusCode.PreconditionFailed) {
49-
super(message, status);
50-
}
51-
}
45+
/**
46+
* Thrown on HTTP response for a resource conflict (usually {@link HttpStatusCode.Conflict}).
47+
*/
48+
export class ConflictError extends HttpError {}
5249

53-
export class RangeError extends HttpError {
54-
constructor(message: string, status = HttpStatusCode.RequestedRangeNotSatisfiable) {
55-
super(message, status);
56-
}
57-
}
50+
/**
51+
* Thrown on HTTP response for a failed precondition or mid-air collision (usually {@link HttpStatusCode.PreconditionFailed}).
52+
*/
53+
export class ConcurrencyError extends HttpError {}

http/HttpStatusCode.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,5 @@ export enum HttpStatusCode {
1010
RequestTimeout = 408,
1111
Conflict = 409,
1212
Gone = 410,
13-
PreconditionFailed = 412,
14-
RequestedRangeNotSatisfiable = 416
13+
PreconditionFailed = 412
1514
}

0 commit comments

Comments
 (0)