Skip to content

Commit a327832

Browse files
committed
errors
1 parent 39a6cf2 commit a327832

File tree

11 files changed

+286
-11
lines changed

11 files changed

+286
-11
lines changed

packages/fetch-api-client/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@
2929
"test:watch": "jest --watch"
3030
},
3131
"keywords": [],
32-
"gitHead": "06e20539d2cfce7522a29d7e9b20a9eb4d78fe2c",
3332
"dependencies": {
34-
"isomorphic-fetch": "^3.0.0"
33+
"isomorphic-fetch": "^3.0.0",
34+
"@interweb/http-errors": "^0.0.1"
35+
3536
},
3637
"devDependencies": {
3738
"@types/isomorphic-fetch": "^0.0.39"

packages/fetch-api-client/src/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
import { createHTTPError } from '@interweb/http-errors';
12
import { URLSearchParams } from 'url';
23

4+
export class NotFoundError extends Error {}
5+
export class UnauthorizedError extends Error {}
6+
export class InternalServerError extends Error {}
7+
8+
39
interface RequestOptions<Params> {
410
hostname: string;
511
path: string;
@@ -130,12 +136,14 @@ export class APIClient {
130136
body: method !== 'GET' && method !== 'DELETE' ? JSON.stringify(params) : null,
131137
};
132138

139+
133140
try {
134141
const response = await fetch(url, fetchOptions);
135142
clearTimeout(id);
136143

137144
if (!response.ok) {
138-
throw new Error(`HTTP error! status: ${response.status}`);
145+
// Use the factory function to create an error based on the status code
146+
throw createHTTPError(response.status);
139147
}
140148

141149
return response.json() as Promise<Resp>;

packages/http-errors/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# @interweb/http-errors
2+
3+
<p align="center">
4+
<img src="https://user-images.githubusercontent.com/545047/188804067-28e67e5e-0214-4449-ab04-2e0c564a6885.svg" width="80"><br />
5+
@interweb/http-errors
6+
</p>
7+
8+
## install
9+
10+
```sh
11+
npm install @interweb/http-errors
12+
```

packages/http-errors/jest.config.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/** @type {import('ts-jest').JestConfigWithTsJest} */
2+
module.exports = {
3+
preset: "ts-jest",
4+
testEnvironment: "node",
5+
transform: {
6+
"^.+\\.tsx?$": [
7+
"ts-jest",
8+
{
9+
babelConfig: false,
10+
tsconfig: "tsconfig.json",
11+
},
12+
],
13+
},
14+
transformIgnorePatterns: [`/node_modules/*`],
15+
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
16+
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
17+
modulePathIgnorePatterns: ["dist/*"]
18+
};

packages/http-errors/package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "@interweb/http-errors",
3+
"version": "0.0.1",
4+
"author": "Dan Lynch <[email protected]>",
5+
"description": "http errors",
6+
"main": "index.js",
7+
"module": "esm/index.js",
8+
"types": "index.d.ts",
9+
"homepage": "https://github.com/cosmology-tech/schema-typescript",
10+
"license": "SEE LICENSE IN LICENSE",
11+
"publishConfig": {
12+
"access": "public",
13+
"directory": "dist"
14+
},
15+
"repository": {
16+
"type": "git",
17+
"url": "https://github.com/cosmology-tech/schema-typescript"
18+
},
19+
"bugs": {
20+
"url": "https://github.com/cosmology-tech/schema-typescript/issues"
21+
},
22+
"scripts": {
23+
"copy": "copyfiles -f ../../LICENSE README.md package.json dist",
24+
"clean": "rimraf dist/**",
25+
"prepare": "npm run build",
26+
"build": "npm run clean; tsc; tsc -p tsconfig.esm.json; npm run copy",
27+
"lint": "eslint . --fix",
28+
"test": "jest",
29+
"test:watch": "jest --watch"
30+
},
31+
"keywords": []
32+
}

packages/http-errors/src/index.ts

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
export class HTTPError extends Error {
2+
constructor(message: string, public status: number) {
3+
super(`${message} | Status: ${status}`);
4+
this.name = this.constructor.name;
5+
}
6+
}
7+
8+
export class BadRequestError extends HTTPError {
9+
constructor() { super('Bad Request', 400); }
10+
}
11+
export class UnauthorizedError extends HTTPError {
12+
constructor() { super('Unauthorized', 401); }
13+
}
14+
export class PaymentRequiredError extends HTTPError {
15+
constructor() { super('Payment Required', 402); }
16+
}
17+
export class ForbiddenError extends HTTPError {
18+
constructor() { super('Forbidden', 403); }
19+
}
20+
export class NotFoundError extends HTTPError {
21+
constructor() { super('Not Found', 404); }
22+
}
23+
export class MethodNotAllowedError extends HTTPError {
24+
constructor() { super('Method Not Allowed', 405); }
25+
}
26+
export class NotAcceptableError extends HTTPError {
27+
constructor() { super('Not Acceptable', 406); }
28+
}
29+
export class ProxyAuthenticationRequiredError extends HTTPError {
30+
constructor() { super('Proxy Authentication Required', 407); }
31+
}
32+
export class RequestTimeoutError extends HTTPError {
33+
constructor() { super('Request Timeout', 408); }
34+
}
35+
export class ConflictError extends HTTPError {
36+
constructor() { super('Conflict', 409); }
37+
}
38+
export class GoneError extends HTTPError {
39+
constructor() { super('Gone', 410); }
40+
}
41+
export class LengthRequiredError extends HTTPError {
42+
constructor() { super('Length Required', 411); }
43+
}
44+
export class PreconditionFailedError extends HTTPError {
45+
constructor() { super('Precondition Failed', 412); }
46+
}
47+
export class PayloadTooLargeError extends HTTPError {
48+
constructor() { super('Payload Too Large', 413); }
49+
}
50+
export class URITooLongError extends HTTPError {
51+
constructor() { super('URI Too Long', 414); }
52+
}
53+
export class UnsupportedMediaTypeError extends HTTPError {
54+
constructor() { super('Unsupported Media Type', 415); }
55+
}
56+
export class RangeNotSatisfiableError extends HTTPError {
57+
constructor() { super('Range Not Satisfiable', 416); }
58+
}
59+
export class ExpectationFailedError extends HTTPError {
60+
constructor() { super('Expectation Failed', 417); }
61+
}
62+
export class ImATeapotError extends HTTPError {
63+
constructor() { super("I'm a Teapot", 418); }
64+
}
65+
export class MisdirectedRequestError extends HTTPError {
66+
constructor() { super('Misdirected Request', 421); }
67+
}
68+
export class UnprocessableEntityError extends HTTPError {
69+
constructor() { super('Unprocessable Entity', 422); }
70+
}
71+
export class LockedError extends HTTPError {
72+
constructor() { super('Locked', 423); }
73+
}
74+
export class FailedDependencyError extends HTTPError {
75+
constructor() { super('Failed Dependency', 424); }
76+
}
77+
export class UpgradeRequiredError extends HTTPError {
78+
constructor() { super('Upgrade Required', 426); }
79+
}
80+
export class PreconditionRequiredError extends HTTPError {
81+
constructor() { super('Precondition Required', 428); }
82+
}
83+
export class TooManyRequestsError extends HTTPError {
84+
constructor() { super('Too Many Requests', 429); }
85+
}
86+
export class RequestHeaderFieldsTooLargeError extends HTTPError {
87+
constructor() { super('Request Header Fields Too Large', 431); }
88+
}
89+
export class UnavailableForLegalReasonsError extends HTTPError {
90+
constructor() { super('Unavailable For Legal Reasons', 451); }
91+
}
92+
export class InternalServerError extends HTTPError {
93+
constructor() { super('Internal Server Error', 500); }
94+
}
95+
export class NotImplementedError extends HTTPError {
96+
constructor() { super('Not Implemented', 501); }
97+
}
98+
export class BadGatewayError extends HTTPError {
99+
constructor() { super('Bad Gateway', 502); }
100+
}
101+
export class ServiceUnavailableError extends HTTPError {
102+
constructor() { super('Service Unavailable', 503); }
103+
}
104+
export class GatewayTimeoutError extends HTTPError {
105+
constructor() { super('Gateway Timeout', 504); }
106+
}
107+
export class HTTPVersionNotSupportedError extends HTTPError {
108+
constructor() { super('HTTP Version Not Supported', 505); }
109+
}
110+
export class VariantAlsoNegotiatesError extends HTTPError {
111+
constructor() { super('Variant Also Negotiates', 506); }
112+
}
113+
export class InsufficientStorageError extends HTTPError {
114+
constructor() { super('Insufficient Storage', 507); }
115+
}
116+
export class LoopDetectedError extends HTTPError {
117+
constructor() { super('Loop Detected', 508); }
118+
}
119+
export class NotExtendedError extends HTTPError {
120+
constructor() { super('Not Extended', 510); }
121+
}
122+
export class NetworkAuthenticationRequiredError extends HTTPError {
123+
constructor() { super('Network Authentication Required', 511); }
124+
}
125+
126+
127+
export function createHTTPError(status: number): HTTPError {
128+
switch (status) {
129+
case 400: return new BadRequestError();
130+
case 401: return new UnauthorizedError();
131+
case 402: return new PaymentRequiredError();
132+
case 403: return new ForbiddenError();
133+
case 404: return new NotFoundError();
134+
case 405: return new MethodNotAllowedError();
135+
case 406: return new NotAcceptableError();
136+
case 407: return new ProxyAuthenticationRequiredError();
137+
case 408: return new RequestTimeoutError();
138+
case 409: return new ConflictError();
139+
case 410: return new GoneError();
140+
case 411: return new LengthRequiredError();
141+
case 412: return new PreconditionFailedError();
142+
case 413: return new PayloadTooLargeError();
143+
case 414: return new URITooLongError();
144+
case 415: return new UnsupportedMediaTypeError();
145+
case 416: return new RangeNotSatisfiableError();
146+
case 417: return new ExpectationFailedError();
147+
case 418: return new ImATeapotError();
148+
case 421: return new MisdirectedRequestError();
149+
case 422: return new UnprocessableEntityError();
150+
case 423: return new LockedError();
151+
case 424: return new FailedDependencyError();
152+
case 426: return new UpgradeRequiredError();
153+
case 428: return new PreconditionRequiredError();
154+
case 429: return new TooManyRequestsError();
155+
case 431: return new RequestHeaderFieldsTooLargeError();
156+
case 451: return new UnavailableForLegalReasonsError();
157+
case 500: return new InternalServerError();
158+
case 501: return new NotImplementedError();
159+
case 502: return new BadGatewayError();
160+
case 503: return new ServiceUnavailableError();
161+
case 504: return new GatewayTimeoutError();
162+
case 505: return new HTTPVersionNotSupportedError();
163+
case 506: return new VariantAlsoNegotiatesError();
164+
case 507: return new InsufficientStorageError();
165+
case 508: return new LoopDetectedError();
166+
case 510: return new NotExtendedError();
167+
case 511: return new NetworkAuthenticationRequiredError();
168+
default: return new HTTPError('Unknown Error', status);
169+
}
170+
}
171+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "dist/esm",
5+
"module": "es2022",
6+
"rootDir": "src/",
7+
"declaration": false
8+
}
9+
}

packages/http-errors/tsconfig.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "dist",
5+
"rootDir": "src/"
6+
},
7+
"include": ["src/**/*.ts"],
8+
"exclude": ["dist", "node_modules", "**/*.spec.*", "**/*.test.*"]
9+
}

packages/node-api-client/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
"test": "jest",
2929
"test:watch": "jest --watch"
3030
},
31-
"keywords": [],
32-
"gitHead": "06e20539d2cfce7522a29d7e9b20a9eb4d78fe2c"
31+
"dependencies": {
32+
"@interweb/http-errors": "^0.0.1"
33+
},
34+
"keywords": []
3335
}

packages/node-api-client/src/index.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createHTTPError } from '@interweb/http-errors';
12
import * as http from 'http';
23
import * as querystring from 'querystring';
34
import { URLSearchParams } from 'url';
@@ -256,11 +257,15 @@ export class APIClient {
256257
data += chunk;
257258
});
258259
res.on('end', () => {
259-
try {
260-
const parsedData: Resp = JSON.parse(data);
261-
resolve(parsedData);
262-
} catch (error) {
263-
reject(error);
260+
if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
261+
reject(createHTTPError(res.statusCode || 500)); // Use 500 as a fallback if status code is undefined
262+
} else {
263+
try {
264+
const parsedData: Resp = JSON.parse(data);
265+
resolve(parsedData);
266+
} catch (error) {
267+
reject(error);
268+
}
264269
}
265270
});
266271
});
@@ -271,7 +276,8 @@ export class APIClient {
271276

272277
req.setTimeout(options.timeout, () => {
273278
req.abort();
274-
reject(new Error('Request timeout'));
279+
// Use createHTTPError for timeout as well, typically 408 Request Timeout
280+
reject(createHTTPError(408));
275281
});
276282

277283
if (options.params && ['POST', 'PUT', 'PATCH'].includes(options.method)) {

0 commit comments

Comments
 (0)