Skip to content
This repository was archived by the owner on Aug 15, 2023. It is now read-only.

Commit 5bac6b9

Browse files
committed
refactor(shared): combine BackendError with APIError
1 parent 6f85623 commit 5bac6b9

File tree

9 files changed

+128
-89
lines changed

9 files changed

+128
-89
lines changed

packages/shared/src/backend/errors/BackendError.ts

-45
This file was deleted.

packages/shared/src/backend/utils/authenticationMiddleware.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NotAuthorizedError } from '../errors/BackendError';
1+
import { NotAuthorizedError } from '../../errors/APIError';
22
import * as express from 'express';
33
import * as E from 'fp-ts/lib/Either';
44
import { pipe } from 'fp-ts/lib/function';

packages/shared/src/backend/utils/routeHandlerMiddleware.ts

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import express from 'express';
22
import _ from 'lodash';
3-
import { APIError } from '../../errors/APIError';
3+
import { isAPIError } from '../../errors/APIError';
44
import { GetLogger } from '../../logger';
55

66
const logger = GetLogger('route-handler');
@@ -93,10 +93,7 @@ export const routeHandleMiddleware = <
9393
} catch (error) {
9494
logger.error('Route handler (%s) error: %O', fname, error);
9595

96-
// todo: this should be 500
97-
res.status(502);
98-
99-
if (error instanceof APIError) {
96+
if (isAPIError(error)) {
10097
logger.error(
10198
'APIError - %s: (%s) %s %s',
10299
error.name,
@@ -105,20 +102,19 @@ export const routeHandleMiddleware = <
105102
);
106103
res.status(error.status);
107104
res.send({
108-
name: error.type,
105+
name: error.name,
109106
message: error.message,
110107
details: error.details,
111108
});
112109
} else if (error instanceof Error) {
113110
logger.error('Error - %s: %s', error.name, error.message);
114-
115-
res.send('Software error: ' + error.message);
116111
logger.error(
117112
'Error in HTTP handler API(%s): %s %s',
118113
fname,
119114
error.message,
120115
error.stack
121116
);
117+
res.status(500).send('Software error: ' + error.message);
122118
} else {
123119
res.status(502);
124120
res.send('Software error: ' + (error as any).message);

packages/shared/src/components/Error/ErrorBox.tsx

+6-4
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ import { isAPIError } from '../../errors/APIError';
77
export const ErrorBox = (e: unknown): React.ReactElement<any, string> => {
88
// eslint-disable-next-line
99
console.log('Displaying error', e);
10+
const errorName = isAPIError(e) ? e.name : 'Error';
11+
const message = isAPIError(e) ? e.message : 'Unknown Error';
1012
return (
1113
<Card>
1214
<Alert severity="error">
13-
<AlertTitle>{e instanceof Error ? e.name : 'Error'}</AlertTitle>
14-
<p>{e instanceof Error ? e.message : 'Unknown error'}</p>
15-
{isAPIError(e) ? (
15+
<AlertTitle>{errorName}</AlertTitle>
16+
<p>{message}</p>
17+
{isAPIError(e) && e.details.kind === 'DecodingError' ? (
1618
<ul>
17-
{(e.details ?? []).map((d) => (
19+
{(e.details.errors ?? []).map((d: any) => (
1820
<li key={d}>{d}</li>
1921
))}
2022
</ul>

packages/shared/src/endpoints/helper.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ const decodeOrThrowRequest = <E extends MinimalEndpointInstance>(
8282
): DecodeRequestSuccess<E>['result'] => {
8383
return pipe(decodeRequest(e, r), (r) => {
8484
if (r.type === 'error') {
85-
throw new APIError(400, 'Bad Request', 'Request decode failed', r.result);
85+
throw new APIError('Bad Request', {
86+
kind: 'DecodingError',
87+
errors: r.result,
88+
});
8689
}
8790

8891
return r.result;
+66-9
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,73 @@
1+
import { AxiosError } from 'axios';
2+
import { IOError } from 'ts-io-error/lib';
3+
14
export const isAPIError = (e: unknown): e is APIError => {
25
return (e as any).name === 'APIError';
36
};
47

5-
export class APIError extends Error {
8+
export class APIError extends IOError {
69
public readonly name = 'APIError';
10+
}
11+
12+
export const NotAuthorizedError = (): APIError => {
13+
return {
14+
name: 'APIError',
15+
status: 401,
16+
message: 'Authorization header is missing',
17+
details: {
18+
kind: 'ClientError',
19+
status: '401',
20+
},
21+
};
22+
};
723

8-
constructor(
9-
public readonly status: number,
10-
public readonly type: string,
11-
public readonly message: string,
12-
public readonly details: string[]
13-
) {
14-
super(message);
24+
export const NotFoundError = (resource: string): APIError => {
25+
return {
26+
name: 'APIError',
27+
status: 404,
28+
message: `Can't find the resource ${resource}`,
29+
details: {
30+
kind: 'ClientError',
31+
status: '404',
32+
},
33+
};
34+
};
35+
36+
export const fromAxiosError = (e: AxiosError): APIError => {
37+
return (
38+
e.response?.data ?? {
39+
name: 'APIError',
40+
status: e.response?.status ?? 500,
41+
message: e.message,
42+
details: {
43+
kind: 'ServerError',
44+
status: '500',
45+
meta: e.response?.data,
46+
},
47+
}
48+
);
49+
};
50+
51+
export const toAPIError = (e: unknown): APIError => {
52+
// eslint-disable-next-line
53+
console.error(e);
54+
if (e instanceof APIError) {
55+
return new APIError(e.message, {
56+
kind: 'ServerError',
57+
status: e.status + '',
58+
meta: e.details,
59+
});
1560
}
16-
}
61+
62+
if (e instanceof Error) {
63+
return new APIError(e.message, {
64+
kind: 'ServerError',
65+
status: e.name,
66+
});
67+
}
68+
69+
return new APIError('UnknownError', {
70+
kind: 'ServerError',
71+
status: 'Unknown',
72+
});
73+
};

packages/shared/src/errors/AppError.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ export class AppError {
1010

1111
export const toAppError = (e: unknown): AppError => {
1212
if (e instanceof APIError) {
13-
return e;
13+
return new AppError(
14+
e.name,
15+
e.message,
16+
e.details.kind === 'DecodingError' ? (e.details.errors as any[]) : []
17+
);
1418
}
1519

1620
if (e instanceof Error) {
+13-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
1+
import { failure } from 'io-ts/lib/PathReporter';
12
import { APIError, isAPIError } from './APIError';
3+
import * as t from 'io-ts';
24

35
export const toValidationError = (
46
message: string,
5-
details: string[]
7+
errors: t.ValidationError[]
68
): APIError => {
7-
return new APIError(400, 'ValidationError', message, details);
9+
return {
10+
name: 'APIError',
11+
message,
12+
status: 400,
13+
details: {
14+
kind: 'DecodingError',
15+
errors: failure(errors),
16+
},
17+
};
818
};
919

1020
export const isValidationError = (
1121
e: unknown
1222
): e is APIError & { type: 'ValidationError' } => {
13-
return isAPIError(e) && e.type === 'ValidationError';
23+
return isAPIError(e) && e.details.kind === 'DecodingError';
1424
};

packages/shared/src/providers/api.provider.ts

+29-17
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import * as TE from 'fp-ts/lib/TaskEither';
1515
import * as t from 'io-ts';
1616
import { PathReporter } from 'io-ts/lib/PathReporter';
1717
import { MinimalEndpointInstance, TypeOfEndpointInstance } from '../endpoints';
18-
import { APIError } from '../errors/APIError';
18+
import { APIError, fromAxiosError, isAPIError } from '../errors/APIError';
1919
import { toValidationError } from '../errors/ValidationError';
2020
import { trexLogger } from '../logger';
2121

@@ -24,21 +24,37 @@ export const apiLogger = trexLogger.extend('API');
2424
export const toAPIError = (e: unknown): APIError => {
2525
// eslint-disable-next-line
2626
apiLogger.error('An error occurred %O', e);
27+
if (isAPIError(e)) {
28+
return e;
29+
}
30+
31+
if (axios.isAxiosError(e)) {
32+
return fromAxiosError(e);
33+
}
34+
2735
if (e instanceof Error) {
2836
if (e.message === 'Network Error') {
29-
return new APIError(
30-
502,
31-
'Network Error',
32-
'The API endpoint is not reachable',
33-
["Be sure you're connected to internet."]
34-
);
37+
return new APIError('Network Error', {
38+
kind: 'NetworkError',
39+
status: '500',
40+
meta: [
41+
'The API endpoint is not reachable',
42+
"Be sure you're connected to internet.",
43+
],
44+
});
3545
}
36-
return new APIError(500, 'UnknownError', e.message, []);
46+
return new APIError(e.message, {
47+
kind: 'ClientError',
48+
meta: e.stack,
49+
status: '500',
50+
});
3751
}
3852

39-
return new APIError(500, 'UnknownError', 'An error occurred', [
40-
JSON.stringify(e),
41-
]);
53+
return new APIError('An error occurred', {
54+
kind: 'ClientError',
55+
meta: JSON.stringify(e),
56+
status: '500',
57+
});
4258
};
4359

4460
const liftFetch = <B>(
@@ -51,11 +67,7 @@ const liftFetch = <B>(
5167
TE.chain((content) => {
5268
return pipe(
5369
decode(content),
54-
E.mapLeft((e): APIError => {
55-
const details = PathReporter.report(E.left(e));
56-
apiLogger.error('toAPIError Validation failed %O', details);
57-
return toValidationError('Validation failed', details);
58-
}),
70+
E.mapLeft((e) => toValidationError('Validation failed', e)),
5971
TE.fromEither
6072
);
6173
})
@@ -128,7 +140,7 @@ export const MakeHTTPClient = (client: AxiosInstance): HTTPClient => {
128140
TE.mapLeft((e): APIError => {
129141
const details = PathReporter.report(E.left(e));
130142
apiLogger.error('MakeHTTPClient Validation failed %O', details);
131-
return toValidationError('Validation failed', details);
143+
return toValidationError('Validation failed', e);
132144
}),
133145
TE.chain((input) => {
134146
const url = e.getPath(input.params);

0 commit comments

Comments
 (0)