@marianmeres/http-utils 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +22 -0
- package/dist/error.d.ts +87 -0
- package/dist/index.cjs +340 -0
- package/dist/index.js +335 -0
- package/dist/status.d.ts +258 -0
- package/package.json +4 -1
- package/.editorconfig +0 -12
- package/.prettierignore +0 -1
- package/.prettierrc.yaml +0 -5
- package/rollup.config.js +0 -15
- package/src/api.ts +0 -198
- package/src/error.ts +0 -162
- package/src/status.ts +0 -107
- package/tests/api.test.js +0 -136
- package/tests/utils.test.js +0 -64
- package/tsconfig.json +0 -113
- /package/{src/index.ts → dist/index.d.ts} +0 -0
package/src/api.ts
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import { dset } from 'dset/merge';
|
|
2
|
-
import { createHttpError } from './error.js';
|
|
3
|
-
|
|
4
|
-
// this is all very opinionated and may not be useful for every use case...
|
|
5
|
-
// there is no magic added over plain fetch calls, just more opinionated and dry api
|
|
6
|
-
|
|
7
|
-
interface BaseParams {
|
|
8
|
-
method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT';
|
|
9
|
-
path: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface FetchParams {
|
|
13
|
-
data?: any;
|
|
14
|
-
token?: string | null;
|
|
15
|
-
headers?: any;
|
|
16
|
-
signal?: any;
|
|
17
|
-
credentials?: null | 'omit' | 'same-origin' | 'include';
|
|
18
|
-
raw?: null | boolean;
|
|
19
|
-
assert?: null | boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
type BaseFetchParams = BaseParams & FetchParams; // Exclude<Drinks, Soda> |
|
|
23
|
-
|
|
24
|
-
const _fetchRaw = async ({
|
|
25
|
-
method,
|
|
26
|
-
path,
|
|
27
|
-
data = null,
|
|
28
|
-
token = null,
|
|
29
|
-
headers = null,
|
|
30
|
-
signal = null,
|
|
31
|
-
credentials,
|
|
32
|
-
}: BaseFetchParams) => {
|
|
33
|
-
headers = Object.entries(headers || {}).map(([k, v]) => ({ [k.toLowerCase()]: v }));
|
|
34
|
-
const opts: any = { method, credentials, headers, signal };
|
|
35
|
-
|
|
36
|
-
if (data) {
|
|
37
|
-
const isObj = typeof data === 'object';
|
|
38
|
-
|
|
39
|
-
// multipart/form-data -- no explicit Content-Type
|
|
40
|
-
if (data instanceof FormData) {
|
|
41
|
-
opts.body = data;
|
|
42
|
-
}
|
|
43
|
-
// cover 99% use cases (may not fit all)
|
|
44
|
-
else {
|
|
45
|
-
// if not stated, assuming json
|
|
46
|
-
if (isObj || !headers['content-type']) {
|
|
47
|
-
opts.headers['content-type'] = 'application/json';
|
|
48
|
-
}
|
|
49
|
-
opts.body = JSON.stringify(data);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// opinionated convention
|
|
54
|
-
if (token) {
|
|
55
|
-
opts.headers['authorization'] = `Bearer ${token}`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return await fetch(path, opts);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const _fetch = async (
|
|
62
|
-
params: BaseFetchParams,
|
|
63
|
-
respHeaders = null,
|
|
64
|
-
_dumpParams = false
|
|
65
|
-
) => {
|
|
66
|
-
if (_dumpParams) return params;
|
|
67
|
-
|
|
68
|
-
const r = await _fetchRaw(params);
|
|
69
|
-
if (params.raw) return r;
|
|
70
|
-
|
|
71
|
-
// quick-n-dirty reference to headers (so it's still accessible over this api wrap)
|
|
72
|
-
if (respHeaders) {
|
|
73
|
-
Object.assign(
|
|
74
|
-
respHeaders,
|
|
75
|
-
[...r.headers.entries()].reduce((m, [k, v]) => ({ ...m, [k]: v }), {}),
|
|
76
|
-
// adding status/text under special keys
|
|
77
|
-
{
|
|
78
|
-
__http_status_code__: r.status,
|
|
79
|
-
__http_status_text__: r.statusText,
|
|
80
|
-
}
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
let body: string = await r.text();
|
|
85
|
-
// prettier-ignore
|
|
86
|
-
try { body = JSON.parse(body); } catch (e) {}
|
|
87
|
-
|
|
88
|
-
params.assert ??= true; // default is true
|
|
89
|
-
|
|
90
|
-
if (!r.ok && params.assert) {
|
|
91
|
-
throw createHttpError(r.status, null, body);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return body;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
export const createHttpApi = (
|
|
98
|
-
base: string | null,
|
|
99
|
-
defaults?: Partial<BaseFetchParams> | (() => Promise<Partial<BaseFetchParams>>)
|
|
100
|
-
) => {
|
|
101
|
-
const _merge = (a: any, b: any): any => {
|
|
102
|
-
const wrap = { result: a };
|
|
103
|
-
dset(wrap, 'result', b);
|
|
104
|
-
return wrap.result;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
const _getDefs = async () => {
|
|
108
|
-
return new Promise<Partial<BaseFetchParams>>(async (resolve) => {
|
|
109
|
-
if (typeof defaults === 'function') {
|
|
110
|
-
resolve(await defaults());
|
|
111
|
-
} else {
|
|
112
|
-
resolve(defaults || {});
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
// GET
|
|
119
|
-
async get(
|
|
120
|
-
path: string,
|
|
121
|
-
params?: FetchParams,
|
|
122
|
-
respHeaders = null,
|
|
123
|
-
_dumpParams = false
|
|
124
|
-
) {
|
|
125
|
-
path = `${base || ''}` + path;
|
|
126
|
-
return _fetch(
|
|
127
|
-
_merge(await _getDefs(), { ...params, method: 'GET', path }),
|
|
128
|
-
respHeaders,
|
|
129
|
-
_dumpParams
|
|
130
|
-
);
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
// POST
|
|
134
|
-
async post(
|
|
135
|
-
path: string,
|
|
136
|
-
data = null,
|
|
137
|
-
params?: FetchParams,
|
|
138
|
-
respHeaders = null,
|
|
139
|
-
_dumpParams = false
|
|
140
|
-
) {
|
|
141
|
-
path = `${base || ''}` + path;
|
|
142
|
-
return _fetch(
|
|
143
|
-
_merge(await _getDefs(), { ...(params || {}), data, method: 'POST', path }),
|
|
144
|
-
respHeaders,
|
|
145
|
-
_dumpParams
|
|
146
|
-
);
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
// PUT
|
|
150
|
-
async put(
|
|
151
|
-
path: string,
|
|
152
|
-
data = null,
|
|
153
|
-
params?: FetchParams,
|
|
154
|
-
respHeaders = null,
|
|
155
|
-
_dumpParams = false
|
|
156
|
-
) {
|
|
157
|
-
path = `${base || ''}` + path;
|
|
158
|
-
return _fetch(
|
|
159
|
-
_merge(await _getDefs(), { ...(params || {}), data, method: 'PUT', path }),
|
|
160
|
-
respHeaders,
|
|
161
|
-
_dumpParams
|
|
162
|
-
);
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
// PATCH
|
|
166
|
-
async patch(
|
|
167
|
-
path: string,
|
|
168
|
-
data = null,
|
|
169
|
-
params?: FetchParams,
|
|
170
|
-
respHeaders = null,
|
|
171
|
-
_dumpParams = false
|
|
172
|
-
) {
|
|
173
|
-
path = `${base || ''}` + path;
|
|
174
|
-
return _fetch(
|
|
175
|
-
_merge(await _getDefs(), { ...(params || {}), data, method: 'PATCH', path }),
|
|
176
|
-
respHeaders,
|
|
177
|
-
_dumpParams
|
|
178
|
-
);
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
// DELETE
|
|
182
|
-
// https://stackoverflow.com/questions/299628/is-an-entity-body-allowed-for-an-http-delete-request
|
|
183
|
-
async del(
|
|
184
|
-
path: string,
|
|
185
|
-
data = null,
|
|
186
|
-
params?: FetchParams,
|
|
187
|
-
respHeaders = null,
|
|
188
|
-
_dumpParams = false
|
|
189
|
-
) {
|
|
190
|
-
path = `${base || ''}` + path;
|
|
191
|
-
return _fetch(
|
|
192
|
-
_merge(await _getDefs(), { ...(params || {}), data, method: 'DELETE', path }),
|
|
193
|
-
respHeaders,
|
|
194
|
-
_dumpParams
|
|
195
|
-
);
|
|
196
|
-
},
|
|
197
|
-
};
|
|
198
|
-
};
|
package/src/error.ts
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { HTTP_STATUS } from './status.js';
|
|
2
|
-
|
|
3
|
-
// opinionated base for all
|
|
4
|
-
class HttpError extends Error {
|
|
5
|
-
public name = 'HttpError';
|
|
6
|
-
public status = HTTP_STATUS.ERROR_SERVER.INTERNAL_SERVER_ERROR.CODE;
|
|
7
|
-
public statusText = HTTP_STATUS.ERROR_SERVER.INTERNAL_SERVER_ERROR.TEXT;
|
|
8
|
-
public body: string | null = null;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// some more specific instances of the well known ones...
|
|
12
|
-
|
|
13
|
-
// client
|
|
14
|
-
|
|
15
|
-
class BadRequest extends HttpError {
|
|
16
|
-
public name = 'HttpBadRequestError';
|
|
17
|
-
public status = HTTP_STATUS.ERROR_CLIENT.BAD_REQUEST.CODE;
|
|
18
|
-
public statusText = HTTP_STATUS.ERROR_CLIENT.BAD_REQUEST.TEXT;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
class Unauthorized extends HttpError {
|
|
22
|
-
public name = 'HttpUnauthorizedError';
|
|
23
|
-
public status = HTTP_STATUS.ERROR_CLIENT.UNAUTHORIZED.CODE;
|
|
24
|
-
public statusText = HTTP_STATUS.ERROR_CLIENT.UNAUTHORIZED.TEXT;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
class Forbidden extends HttpError {
|
|
28
|
-
public name = 'HttpForbiddenError';
|
|
29
|
-
public status = HTTP_STATUS.ERROR_CLIENT.FORBIDDEN.CODE;
|
|
30
|
-
public statusText = HTTP_STATUS.ERROR_CLIENT.FORBIDDEN.TEXT;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
class NotFound extends HttpError {
|
|
34
|
-
public name = 'HttpNotFoundError';
|
|
35
|
-
public status = HTTP_STATUS.ERROR_CLIENT.NOT_FOUND.CODE;
|
|
36
|
-
public statusText = HTTP_STATUS.ERROR_CLIENT.NOT_FOUND.TEXT;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
class MethodNotAllowed extends HttpError {
|
|
40
|
-
public name = 'HttpMethodNotAllowedError';
|
|
41
|
-
public status = HTTP_STATUS.ERROR_CLIENT.METHOD_NOT_ALLOWED.CODE;
|
|
42
|
-
public statusText = HTTP_STATUS.ERROR_CLIENT.METHOD_NOT_ALLOWED.TEXT;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
class RequestTimeout extends HttpError {
|
|
46
|
-
public name = 'HttpRequestTimeoutError';
|
|
47
|
-
public status = HTTP_STATUS.ERROR_CLIENT.REQUEST_TIMEOUT.CODE;
|
|
48
|
-
public statusText = HTTP_STATUS.ERROR_CLIENT.REQUEST_TIMEOUT.TEXT;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
class Conflict extends HttpError {
|
|
52
|
-
public name = 'HttpConflictError';
|
|
53
|
-
public status = HTTP_STATUS.ERROR_CLIENT.CONFLICT.CODE;
|
|
54
|
-
public statusText = HTTP_STATUS.ERROR_CLIENT.CONFLICT.TEXT;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
class Gone extends HttpError {
|
|
58
|
-
public name = 'HttpGoneError';
|
|
59
|
-
public status = HTTP_STATUS.ERROR_CLIENT.GONE.CODE;
|
|
60
|
-
public statusText = HTTP_STATUS.ERROR_CLIENT.GONE.TEXT;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
class ImATeapot extends HttpError {
|
|
64
|
-
public name = 'HttpImATeapotError';
|
|
65
|
-
public status = HTTP_STATUS.ERROR_CLIENT.IM_A_TEAPOT.CODE;
|
|
66
|
-
public statusText = HTTP_STATUS.ERROR_CLIENT.IM_A_TEAPOT.TEXT;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// server
|
|
70
|
-
|
|
71
|
-
class InternalServerError extends HttpError {
|
|
72
|
-
public name = 'HttpInternalServerError';
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
class NotImplemented extends HttpError {
|
|
76
|
-
public name = 'HttpServiceUnavailableError';
|
|
77
|
-
public status = HTTP_STATUS.ERROR_SERVER.NOT_IMPLEMENTED.CODE;
|
|
78
|
-
public statusText = HTTP_STATUS.ERROR_SERVER.NOT_IMPLEMENTED.TEXT;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
class BadGateway extends HttpError {
|
|
82
|
-
public name = 'HttpBadGatewayError';
|
|
83
|
-
public status = HTTP_STATUS.ERROR_SERVER.BAD_GATEWAY.CODE;
|
|
84
|
-
public statusText = HTTP_STATUS.ERROR_SERVER.BAD_GATEWAY.TEXT;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
class ServiceUnavailable extends HttpError {
|
|
88
|
-
public name = 'HttpServiceUnavailableError';
|
|
89
|
-
public status = HTTP_STATUS.ERROR_SERVER.SERVICE_UNAVAILABLE.CODE;
|
|
90
|
-
public statusText = HTTP_STATUS.ERROR_SERVER.SERVICE_UNAVAILABLE.TEXT;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
export const HTTP_ERROR = {
|
|
95
|
-
// base
|
|
96
|
-
HttpError,
|
|
97
|
-
// client
|
|
98
|
-
BadRequest,
|
|
99
|
-
Unauthorized,
|
|
100
|
-
Forbidden,
|
|
101
|
-
NotFound,
|
|
102
|
-
MethodNotAllowed,
|
|
103
|
-
RequestTimeout,
|
|
104
|
-
Conflict,
|
|
105
|
-
Gone,
|
|
106
|
-
ImATeapot,
|
|
107
|
-
// server
|
|
108
|
-
InternalServerError,
|
|
109
|
-
NotImplemented,
|
|
110
|
-
BadGateway,
|
|
111
|
-
ServiceUnavailable,
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const _wellKnownCtorMap = {
|
|
115
|
-
'400': BadRequest,
|
|
116
|
-
'401': Unauthorized,
|
|
117
|
-
'403': Forbidden,
|
|
118
|
-
'404': NotFound,
|
|
119
|
-
'405': MethodNotAllowed,
|
|
120
|
-
'408': RequestTimeout,
|
|
121
|
-
'409': Conflict,
|
|
122
|
-
'410': Gone,
|
|
123
|
-
'418': ImATeapot,
|
|
124
|
-
//
|
|
125
|
-
'500': InternalServerError,
|
|
126
|
-
'501': NotImplemented,
|
|
127
|
-
'502': BadGateway,
|
|
128
|
-
'503': ServiceUnavailable,
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause
|
|
132
|
-
export const createHttpError = (
|
|
133
|
-
code: number | string,
|
|
134
|
-
message?: string | null,
|
|
135
|
-
body?: any,
|
|
136
|
-
cause?: any
|
|
137
|
-
) => {
|
|
138
|
-
const fallback = HTTP_STATUS.ERROR_SERVER.INTERNAL_SERVER_ERROR;
|
|
139
|
-
|
|
140
|
-
code = Number(code);
|
|
141
|
-
if (isNaN(code) || !(code >= 400 && code < 600)) code = fallback.CODE;
|
|
142
|
-
|
|
143
|
-
const found = HTTP_STATUS.findByCode(code);
|
|
144
|
-
const statusText = found?.TEXT ?? fallback.TEXT;
|
|
145
|
-
|
|
146
|
-
// opinionated convention
|
|
147
|
-
if (typeof body === 'string') {
|
|
148
|
-
// prettier-ignore
|
|
149
|
-
try { body = JSON.parse(body); } catch (e) {}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// try to find the well known one, otherwise fallback to generic
|
|
153
|
-
const ctor =
|
|
154
|
-
_wellKnownCtorMap[`${code}` as keyof typeof _wellKnownCtorMap] ?? HttpError;
|
|
155
|
-
|
|
156
|
-
let e = new ctor(message || statusText, { cause });
|
|
157
|
-
e.status = found?.CODE ?? fallback.CODE;
|
|
158
|
-
e.statusText = statusText;
|
|
159
|
-
e.body = body;
|
|
160
|
-
|
|
161
|
-
return e;
|
|
162
|
-
};
|
package/src/status.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
export class HTTP_STATUS {
|
|
2
|
-
// 1xx
|
|
3
|
-
// prettier-ignore
|
|
4
|
-
static readonly INFO = {
|
|
5
|
-
CONTINUE: { CODE: 100, TEXT: 'Continue' },
|
|
6
|
-
SWITCHING_PROTOCOLS: { CODE: 101, TEXT: 'Switching Protocols' },
|
|
7
|
-
PROCESSING: { CODE: 102, TEXT: 'Processing' },
|
|
8
|
-
EARLY_HINTS: { CODE: 103, TEXT: 'Early Hints' },
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
// 2xx
|
|
12
|
-
// prettier-ignore
|
|
13
|
-
static readonly SUCCESS = {
|
|
14
|
-
OK: { CODE: 200, TEXT: 'OK' },
|
|
15
|
-
NON_AUTHORITATIVE_INFO: { CODE: 203, TEXT: 'Non-Authoritative Information' },
|
|
16
|
-
ACCEPTED: { CODE: 202, TEXT: 'Accepted' },
|
|
17
|
-
NO_CONTENT: { CODE: 204, TEXT: 'No Content' },
|
|
18
|
-
RESET_CONTENT: { CODE: 205, TEXT: 'Reset Content' },
|
|
19
|
-
PARTIAL_CONTENT: { CODE: 206, TEXT: 'Partial Content' },
|
|
20
|
-
MULTI_STATUS: { CODE: 207, TEXT: 'Multi-Status' },
|
|
21
|
-
ALREADY_REPORTED: { CODE: 208, TEXT: 'Already Reported' },
|
|
22
|
-
IM_USED: { CODE: 226, TEXT: 'IM Used' }, // ?
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// 3xx
|
|
26
|
-
// prettier-ignore
|
|
27
|
-
static readonly REDIRECT = {
|
|
28
|
-
MUTLIPLE_CHOICES: { CODE: 300, TEXT: 'Multiple Choices' },
|
|
29
|
-
MOVED_PERMANENTLY: { CODE: 301, TEXT: 'Moved Permanently' },
|
|
30
|
-
FOUND: { CODE: 302, TEXT: 'Found' },
|
|
31
|
-
SEE_OTHER: { CODE: 303, TEXT: 'See Other' },
|
|
32
|
-
NOT_MODIFIED: { CODE: 304, TEXT: 'Not Modified' },
|
|
33
|
-
TEMPORARY_REDIRECT: { CODE: 307, TEXT: 'Temporary Redirect' },
|
|
34
|
-
PERMANENT_REDIRECT: { CODE: 308, TEXT: 'Permanent Redirect' },
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// 4xx
|
|
38
|
-
// prettier-ignore
|
|
39
|
-
static readonly ERROR_CLIENT = {
|
|
40
|
-
BAD_REQUEST: { CODE: 400, TEXT: 'Bad Request' },
|
|
41
|
-
UNAUTHORIZED: { CODE: 401, TEXT: 'Unauthorized' },
|
|
42
|
-
PAYMENT_REQUIRED_EXPERIMENTAL: { CODE: 402, TEXT: 'Payment Required Experimental' },
|
|
43
|
-
FORBIDDEN: { CODE: 403, TEXT: 'Forbidden' },
|
|
44
|
-
NOT_FOUND: { CODE: 404, TEXT: 'Not Found' },
|
|
45
|
-
METHOD_NOT_ALLOWED: { CODE: 405, TEXT: 'Method Not Allowed' },
|
|
46
|
-
NOT_ACCEPTABLE: { CODE: 406, TEXT: 'Not Acceptable' },
|
|
47
|
-
PROXY_AUTHENTICATION_REQUIRED: { CODE: 407, TEXT: 'Proxy Authentication Required' },
|
|
48
|
-
REQUEST_TIMEOUT: { CODE: 408, TEXT: 'Request Timeout' },
|
|
49
|
-
CONFLICT: { CODE: 409, TEXT: 'Conflict' },
|
|
50
|
-
GONE: { CODE: 410, TEXT: 'Gone' },
|
|
51
|
-
LENGTH_REQUIRED: { CODE: 411, TEXT: 'Length Required' },
|
|
52
|
-
PRECONDITION_FAILED: { CODE: 412, TEXT: 'Precondition Failed' },
|
|
53
|
-
PAYLOAD_TOO_LARGE: { CODE: 413, TEXT: 'Payload Too Large' },
|
|
54
|
-
URI_TOO_LONG: { CODE: 414, TEXT: 'URI Too Long' },
|
|
55
|
-
UNSUPPORTED_MEDIA_TYPE: { CODE: 415, TEXT: 'Unsupported Media Type' },
|
|
56
|
-
RANGE_NOT_SATISFIABLE: { CODE: 416, TEXT: 'Range Not Satisfiable' },
|
|
57
|
-
EXPECTATION_FAILED: { CODE: 417, TEXT: 'Expectation Failed' },
|
|
58
|
-
IM_A_TEAPOT: { CODE: 418, TEXT: "I'm a teapot" },
|
|
59
|
-
MISDIRECTED_REQUEST: { CODE: 421, TEXT: 'Misdirected Request' },
|
|
60
|
-
UNPROCESSABLE_CONTENT: { CODE: 422, TEXT: 'Unprocessable Content' },
|
|
61
|
-
LOCKED: { CODE: 423, TEXT: 'Locked' },
|
|
62
|
-
FAILED_DEPENDENCY: { CODE: 424, TEXT: 'Failed Dependency' },
|
|
63
|
-
TOO_EARLY_EXPERIMENTAL: { CODE: 425, TEXT: 'Too Early Experimental' },
|
|
64
|
-
UPGRADE_REQUIRED: { CODE: 426, TEXT: 'Upgrade Required' },
|
|
65
|
-
PRECONDITION_REQUIRED: { CODE: 428, TEXT: 'Precondition Required' },
|
|
66
|
-
TOO_MANY_REQUESTS: { CODE: 429, TEXT: 'Too Many Requests' },
|
|
67
|
-
REQUEST_HEADER_FIELDS_TOO_LARGE: { CODE: 431, TEXT: 'Request Header Fields Too Large' },
|
|
68
|
-
UNAVAILABLE_FOR_LEGAL_REASONS: { CODE: 451, TEXT: 'Unavailable For Legal Reasons' },
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// 5xx
|
|
72
|
-
// prettier-ignore
|
|
73
|
-
static readonly ERROR_SERVER = {
|
|
74
|
-
INTERNAL_SERVER_ERROR: { CODE: 500, TEXT: 'Internal Server Error' },
|
|
75
|
-
NOT_IMPLEMENTED: { CODE: 501, TEXT: 'Not Implemented' },
|
|
76
|
-
BAD_GATEWAY: { CODE: 502, TEXT: 'Bad Gateway' },
|
|
77
|
-
SERVICE_UNAVAILABLE: { CODE: 503, TEXT: 'Service Unavailable' },
|
|
78
|
-
GATEWAY_TIMEOUT: { CODE: 504, TEXT: 'Gateway Timeout' },
|
|
79
|
-
HTTP_VERSION_NOT_SUPPORTED: { CODE: 505, TEXT: 'HTTP Version Not Supported' },
|
|
80
|
-
VARIANT_ALSO_NEGOTIATES: { CODE: 506, TEXT: 'Variant Also Negotiates' },
|
|
81
|
-
INSUFFICIENT_STORAGE: { CODE: 507, TEXT: 'Insufficient Storage' },
|
|
82
|
-
LOOP_DETECTED: { CODE: 508, TEXT: 'Loop Detected' },
|
|
83
|
-
NOT_EXTENDED: { CODE: 510, TEXT: 'Not Extended' },
|
|
84
|
-
NETWORK_AUTH_REQUIRED: { CODE: 511, TEXT: 'Network Authentication Required' },
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
static findByCode(
|
|
89
|
-
code: number | string
|
|
90
|
-
): { CODE: number; TEXT: string; _TYPE: string; _KEY: string } | null {
|
|
91
|
-
const keys: (keyof typeof HTTP_STATUS)[] = [
|
|
92
|
-
'INFO',
|
|
93
|
-
'SUCCESS',
|
|
94
|
-
'REDIRECT',
|
|
95
|
-
'ERROR_CLIENT',
|
|
96
|
-
'ERROR_SERVER',
|
|
97
|
-
];
|
|
98
|
-
for (const _TYPE of keys) {
|
|
99
|
-
for (const [_KEY, data] of Object.entries(HTTP_STATUS[_TYPE]) as any) {
|
|
100
|
-
if (data.CODE == code) {
|
|
101
|
-
return { ...data, _TYPE, _KEY };
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
}
|
package/tests/api.test.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { TestRunner } from '@marianmeres/test-runner';
|
|
2
|
-
import { strict as assert } from 'node:assert';
|
|
3
|
-
import { createServer } from 'node:http';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { HTTP_ERROR, createHttpApi } from '../dist/index.cjs';
|
|
7
|
-
|
|
8
|
-
let server;
|
|
9
|
-
const hostname = '127.0.0.1';
|
|
10
|
-
const port = 3456;
|
|
11
|
-
const url = `http://${hostname}:${port}`;
|
|
12
|
-
|
|
13
|
-
// https://nodejs.org/en/learn/modules/anatomy-of-an-http-transaction
|
|
14
|
-
const collectBody = async (request) =>
|
|
15
|
-
new Promise((resolve) => {
|
|
16
|
-
let body = [];
|
|
17
|
-
request
|
|
18
|
-
.on('data', (chunk) => body.push(chunk))
|
|
19
|
-
.on('end', () => resolve(Buffer.concat(body).toString()));
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const suite = new TestRunner(path.basename(fileURLToPath(import.meta.url)), {
|
|
23
|
-
beforeEach: async () => {
|
|
24
|
-
server = createServer(async (req, res) => {
|
|
25
|
-
res.setHeader('Content-Type', 'application/json');
|
|
26
|
-
if (req.url === '/echo') {
|
|
27
|
-
res.statusCode = 200;
|
|
28
|
-
if (req.method === 'POST') {
|
|
29
|
-
res.end(await collectBody(req));
|
|
30
|
-
} else {
|
|
31
|
-
res.end('{"foo":"bar"}');
|
|
32
|
-
}
|
|
33
|
-
} else {
|
|
34
|
-
res.statusCode = 404;
|
|
35
|
-
res.end('{"some":{"deep":"message"}}');
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
return new Promise((resolve) => server.listen(port, hostname, resolve));
|
|
39
|
-
},
|
|
40
|
-
afterEach: async () => new Promise((resolve) => server.close(resolve)),
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
suite.test('createHttpApi GET', async () => {
|
|
44
|
-
let api = createHttpApi();
|
|
45
|
-
let respHeaders = {};
|
|
46
|
-
|
|
47
|
-
let r = await api.get(`${url}/echo`, {}, respHeaders);
|
|
48
|
-
assert(r.foo === 'bar');
|
|
49
|
-
assert(respHeaders.__http_status_code__ === 200);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
suite.test('createHttpApi base option', async () => {
|
|
53
|
-
let api = createHttpApi(url);
|
|
54
|
-
let respHeaders = {};
|
|
55
|
-
|
|
56
|
-
let r = await api.get(`/echo`, {}, respHeaders);
|
|
57
|
-
assert(r.foo === 'bar');
|
|
58
|
-
assert(respHeaders.__http_status_code__ === 200);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
suite.test('createHttpApi RAW', async () => {
|
|
62
|
-
let api = createHttpApi();
|
|
63
|
-
let headers = {};
|
|
64
|
-
|
|
65
|
-
// raw
|
|
66
|
-
let r = await api.get(`${url}/echo`, { raw: true });
|
|
67
|
-
assert(r instanceof Response);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
suite.test('createHttpApi error', async () => {
|
|
71
|
-
let api = createHttpApi();
|
|
72
|
-
|
|
73
|
-
let err;
|
|
74
|
-
try {
|
|
75
|
-
let r = await api.get(`${url}/asdf`);
|
|
76
|
-
assert(false); // must not be reached
|
|
77
|
-
} catch (e) {
|
|
78
|
-
err = e;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
assert(err instanceof HTTP_ERROR.NotFound);
|
|
82
|
-
assert(err.body.some.deep === 'message');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
suite.test('createHttpApi error { raw: true }', async () => {
|
|
86
|
-
let api = createHttpApi();
|
|
87
|
-
|
|
88
|
-
let r = await api.get(`${url}/asdf`, { raw: true });
|
|
89
|
-
assert(r instanceof Response);
|
|
90
|
-
assert(!r.ok);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
suite.test('createHttpApi error { assert: false } does not throw', async () => {
|
|
94
|
-
let api = createHttpApi();
|
|
95
|
-
let respHeaders = {};
|
|
96
|
-
|
|
97
|
-
let r = await api.get(`${url}/asdf`, { assert: false }, respHeaders);
|
|
98
|
-
assert(r.some.deep === 'message');
|
|
99
|
-
assert(respHeaders.__http_status_code__ === 404);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
suite.test('createHttpApi POST', async () => {
|
|
103
|
-
let api = createHttpApi();
|
|
104
|
-
let respHeaders = {};
|
|
105
|
-
|
|
106
|
-
let r = await api.post(`${url}/echo`, { hey: 'ho' }, {}, respHeaders);
|
|
107
|
-
assert(r.hey === 'ho');
|
|
108
|
-
assert(respHeaders.__http_status_code__ === 200);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
suite.test('createHttpApi merge default params', async () => {
|
|
112
|
-
let api = createHttpApi(null, {
|
|
113
|
-
headers: { authorization: 'Bearer foo' },
|
|
114
|
-
method: 'must be ignored',
|
|
115
|
-
path: 'must be ignored',
|
|
116
|
-
credentials: 'include',
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
const params = await api.post(
|
|
120
|
-
'/hoho',
|
|
121
|
-
{ foo: 'bar' },
|
|
122
|
-
{ headers: { hey: 'ho' } },
|
|
123
|
-
null,
|
|
124
|
-
true
|
|
125
|
-
);
|
|
126
|
-
// console.log(params);
|
|
127
|
-
|
|
128
|
-
assert(params.headers.authorization === 'Bearer foo');
|
|
129
|
-
assert(params.headers.hey === 'ho');
|
|
130
|
-
assert(params.method === 'POST');
|
|
131
|
-
assert(params.path === '/hoho');
|
|
132
|
-
assert(params.credentials === 'include');
|
|
133
|
-
assert(params.data.foo === 'bar');
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
export default suite;
|
package/tests/utils.test.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { TestRunner } from '@marianmeres/test-runner';
|
|
2
|
-
import { strict as assert } from 'node:assert';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { HTTP_ERROR, HTTP_STATUS, createHttpError } from '../dist/index.cjs';
|
|
6
|
-
|
|
7
|
-
const suite = new TestRunner(path.basename(fileURLToPath(import.meta.url)));
|
|
8
|
-
|
|
9
|
-
suite.test('HTTP_STATUS.findByCode', () => {
|
|
10
|
-
const s = HTTP_STATUS.findByCode(200);
|
|
11
|
-
assert(s.CODE === 200);
|
|
12
|
-
assert(s.TEXT === 'OK');
|
|
13
|
-
assert(s._TYPE === 'SUCCESS');
|
|
14
|
-
assert(s._KEY === 'OK');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
suite.test('HTTP_ERROR', () => {
|
|
18
|
-
let e = new HTTP_ERROR.HttpError();
|
|
19
|
-
assert(e.toString() === 'HttpError');
|
|
20
|
-
assert(e.status === HTTP_STATUS.ERROR_SERVER.INTERNAL_SERVER_ERROR.CODE);
|
|
21
|
-
|
|
22
|
-
// random client pick
|
|
23
|
-
e = new HTTP_ERROR.Unauthorized('Foo');
|
|
24
|
-
assert(e.toString() === 'HttpUnauthorizedError: Foo');
|
|
25
|
-
assert(e.status === HTTP_STATUS.ERROR_CLIENT.UNAUTHORIZED.CODE);
|
|
26
|
-
assert(e.statusText === HTTP_STATUS.ERROR_CLIENT.UNAUTHORIZED.TEXT);
|
|
27
|
-
|
|
28
|
-
// random server pick
|
|
29
|
-
e = new HTTP_ERROR.BadGateway('Foo');
|
|
30
|
-
assert(e.toString() === 'HttpBadGatewayError: Foo');
|
|
31
|
-
assert(e.status === HTTP_STATUS.ERROR_SERVER.BAD_GATEWAY.CODE);
|
|
32
|
-
assert(e.statusText === HTTP_STATUS.ERROR_SERVER.BAD_GATEWAY.TEXT);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
suite.test('createHttpErrorByCode', () => {
|
|
36
|
-
// well known
|
|
37
|
-
let e = createHttpError(404, null, '{"foo":"bar"}', { baz: 'bat' });
|
|
38
|
-
|
|
39
|
-
assert(e instanceof HTTP_ERROR.NotFound);
|
|
40
|
-
assert(e.toString() === 'HttpNotFoundError: Not Found');
|
|
41
|
-
assert(e.body.foo === 'bar');
|
|
42
|
-
assert(e.cause.baz === 'bat');
|
|
43
|
-
|
|
44
|
-
// NOT well known
|
|
45
|
-
e = createHttpError(423, null, '{invalid json}', 123);
|
|
46
|
-
assert(e instanceof HTTP_ERROR.HttpError);
|
|
47
|
-
assert(e.toString() === 'HttpError: Locked');
|
|
48
|
-
assert(e.body === '{invalid json}');
|
|
49
|
-
assert(e.cause === 123);
|
|
50
|
-
|
|
51
|
-
// unknown code must fall back to 500
|
|
52
|
-
e = createHttpError(123, null, '123');
|
|
53
|
-
assert(e instanceof HTTP_ERROR.InternalServerError);
|
|
54
|
-
assert(e.toString() === 'HttpInternalServerError: Internal Server Error');
|
|
55
|
-
assert(e.body === 123); // '123' is a valid json string
|
|
56
|
-
assert(e.cause === undefined);
|
|
57
|
-
|
|
58
|
-
// custom message
|
|
59
|
-
e = createHttpError(123, 'Hey', '123');
|
|
60
|
-
assert(e.toString() === 'HttpInternalServerError: Hey');
|
|
61
|
-
assert(e.body === 123);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
export default suite;
|