@e22m4u/js-trie-router 0.6.5 → 0.6.6
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/README.md +20 -21
- package/dist/cjs/index.cjs +26 -31
- package/package.json +1 -1
- package/src/parsers/body-parser.d.ts +0 -10
- package/src/parsers/body-parser.js +12 -38
- package/src/parsers/body-parser.spec.js +63 -48
- package/src/trie-router.spec.js +657 -24
- package/src/utils/create-request-mock.js +5 -1
- package/src/utils/has-request-body.d.ts +10 -0
- package/src/utils/has-request-body.js +21 -0
- package/src/utils/has-request-body.spec.js +23 -0
- package/src/utils/index.d.ts +1 -0
- package/src/utils/index.js +1 -0
package/README.md
CHANGED
|
@@ -190,39 +190,38 @@ router.defineRoute({
|
|
|
190
190
|
|
|
191
191
|
**При получении входящего HTTP-запроса**
|
|
192
192
|
|
|
193
|
-
1. [Глобальные хуки `onRequest`](#onrequest)
|
|
194
|
-
\- Вызываются до поиска маршрута и
|
|
195
|
-
блокировки запроса или установки базовых заголовков.
|
|
193
|
+
1. [Глобальные хуки `onRequest`](#onrequest).
|
|
194
|
+
\- Вызываются до поиска маршрута и разбора входящих данных.
|
|
196
195
|
|
|
197
|
-
2. Поиск
|
|
198
|
-
\-
|
|
199
|
-
|
|
196
|
+
2. Поиск маршрута.
|
|
197
|
+
\- Если маршрут не найден, отправляется ответ `404`, а дальнейшая обработка
|
|
198
|
+
прерывается.
|
|
200
199
|
|
|
201
|
-
3. Создание `RequestContext` и
|
|
200
|
+
3. Создание `RequestContext` и парсинг.
|
|
202
201
|
\- Создается экземпляр контекста, разбирается строка запроса, заголовки
|
|
203
202
|
и извлекается тело запроса.
|
|
204
203
|
|
|
205
|
-
4. [Глобальные хуки `preHandler`](#prehandler-глобальный)
|
|
206
|
-
\- Вызываются последовательно. Если хук возвращает значение
|
|
207
|
-
|
|
204
|
+
4. [Глобальные хуки `preHandler`](#prehandler-глобальный).
|
|
205
|
+
\- Вызываются последовательно. Если хук возвращает значение или отправляет
|
|
206
|
+
ответ, цикл прерывается.
|
|
208
207
|
|
|
209
|
-
5. [Хуки маршрута `preHandler`](#prehandler-для-маршрута)
|
|
210
|
-
\- Вызываются
|
|
211
|
-
|
|
208
|
+
5. [Хуки маршрута `preHandler`](#prehandler-для-маршрута).
|
|
209
|
+
\- Вызываются для конкретного маршрута. Правила прерывания такие же,
|
|
210
|
+
как у глобальных хуков.
|
|
212
211
|
|
|
213
|
-
6. Основной обработчик (функция `handler`)
|
|
214
|
-
\-
|
|
212
|
+
6. Основной обработчик (функция `handler`).
|
|
213
|
+
\- Обработчик запроса вызывается для формирования ответа сервера.
|
|
215
214
|
|
|
216
|
-
7. [Хуки маршрута `postHandler`](#posthandler-для-маршрута)
|
|
217
|
-
\-
|
|
218
|
-
|
|
215
|
+
7. [Хуки маршрута `postHandler`](#posthandler-для-маршрута).
|
|
216
|
+
\- Получают результат обработчика и могут трансформировать его перед
|
|
217
|
+
отправкой.
|
|
219
218
|
|
|
220
|
-
8. [Глобальные хуки `postHandler`](#posthandler-глобальный)
|
|
219
|
+
8. [Глобальные хуки `postHandler`](#posthandler-глобальный).
|
|
221
220
|
\- Завершают цепочку трансформации ответа.
|
|
222
221
|
|
|
223
222
|
9. Отправка ответа.
|
|
224
|
-
\- Маршрутизатор
|
|
225
|
-
|
|
223
|
+
\- Маршрутизатор сериализует итоговые данные, устанавливает заголовки
|
|
224
|
+
и отправляет клиенту.
|
|
226
225
|
|
|
227
226
|
Процесс обработки запроса обернут в глобальный `try/catch` блок. Любая ошибка,
|
|
228
227
|
выброшенная на любом этапе (в хуках, при разборе тела или в самом обработчике),
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -38,7 +38,6 @@ __export(index_exports, {
|
|
|
38
38
|
EXPOSED_ERROR_PROPERTIES: () => EXPOSED_ERROR_PROPERTIES,
|
|
39
39
|
ErrorSender: () => ErrorSender,
|
|
40
40
|
HttpMethod: () => HttpMethod,
|
|
41
|
-
METHODS_WITH_BODY: () => METHODS_WITH_BODY,
|
|
42
41
|
QueryParser: () => QueryParser,
|
|
43
42
|
ROOT_PATH: () => ROOT_PATH,
|
|
44
43
|
ROUTER_HOOK_TYPES: () => ROUTER_HOOK_TYPES,
|
|
@@ -51,7 +50,6 @@ __export(index_exports, {
|
|
|
51
50
|
RouterHookType: () => RouterHookType,
|
|
52
51
|
RouterOptions: () => RouterOptions,
|
|
53
52
|
TrieRouter: () => TrieRouter,
|
|
54
|
-
UNPARSABLE_MEDIA_TYPES: () => UNPARSABLE_MEDIA_TYPES,
|
|
55
53
|
cloneDeep: () => cloneDeep,
|
|
56
54
|
createCookieString: () => createCookieString,
|
|
57
55
|
createError: () => createError,
|
|
@@ -60,6 +58,7 @@ __export(index_exports, {
|
|
|
60
58
|
createRouteMock: () => createRouteMock,
|
|
61
59
|
fetchRequestBody: () => fetchRequestBody,
|
|
62
60
|
getRequestPathname: () => getRequestPathname,
|
|
61
|
+
hasRequestBody: () => hasRequestBody,
|
|
63
62
|
isPromise: () => isPromise,
|
|
64
63
|
isReadableStream: () => isReadableStream,
|
|
65
64
|
isResponseSent: () => isResponseSent,
|
|
@@ -219,6 +218,18 @@ function isResponseSent(response) {
|
|
|
219
218
|
}
|
|
220
219
|
__name(isResponseSent, "isResponseSent");
|
|
221
220
|
|
|
221
|
+
// src/utils/has-request-body.js
|
|
222
|
+
function hasRequestBody(request) {
|
|
223
|
+
if (request.headers["transfer-encoding"] !== void 0) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
if (!isNaN(request.headers["content-length"]) && request.headers["content-length"] !== "0") {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
__name(hasRequestBody, "hasRequestBody");
|
|
232
|
+
|
|
222
233
|
// src/utils/create-route-mock.js
|
|
223
234
|
function createRouteMock(options = {}) {
|
|
224
235
|
return new Route({
|
|
@@ -641,7 +652,7 @@ function createRequestHeaders(host, secure, body, cookies, encoding, headers) {
|
|
|
641
652
|
obj["content-type"] = "application/json";
|
|
642
653
|
}
|
|
643
654
|
}
|
|
644
|
-
if (body != null && obj["content-length"] == null) {
|
|
655
|
+
if (body != null && obj["transfer-encoding"] == null && obj["content-length"] == null) {
|
|
645
656
|
if (typeof body === "string") {
|
|
646
657
|
const length = Buffer.byteLength(body, encoding);
|
|
647
658
|
obj["content-length"] = String(length);
|
|
@@ -1558,8 +1569,6 @@ var RouterOptions = _RouterOptions;
|
|
|
1558
1569
|
|
|
1559
1570
|
// src/parsers/body-parser.js
|
|
1560
1571
|
var import_js_format15 = require("@e22m4u/js-format");
|
|
1561
|
-
var METHODS_WITH_BODY = ["POST", "PUT", "PATCH", "DELETE"];
|
|
1562
|
-
var UNPARSABLE_MEDIA_TYPES = ["multipart/form-data"];
|
|
1563
1572
|
var _BodyParser = class _BodyParser extends DebuggableService {
|
|
1564
1573
|
/**
|
|
1565
1574
|
* Parsers.
|
|
@@ -1590,7 +1599,7 @@ var _BodyParser = class _BodyParser extends DebuggableService {
|
|
|
1590
1599
|
parser
|
|
1591
1600
|
);
|
|
1592
1601
|
}
|
|
1593
|
-
this._parsers[mediaType] = parser;
|
|
1602
|
+
this._parsers[mediaType.toLowerCase()] = parser;
|
|
1594
1603
|
return this;
|
|
1595
1604
|
}
|
|
1596
1605
|
/**
|
|
@@ -1606,7 +1615,7 @@ var _BodyParser = class _BodyParser extends DebuggableService {
|
|
|
1606
1615
|
mediaType
|
|
1607
1616
|
);
|
|
1608
1617
|
}
|
|
1609
|
-
return Boolean(this._parsers[mediaType]);
|
|
1618
|
+
return Boolean(this._parsers[mediaType.toLowerCase()]);
|
|
1610
1619
|
}
|
|
1611
1620
|
/**
|
|
1612
1621
|
* Get parser.
|
|
@@ -1621,7 +1630,7 @@ var _BodyParser = class _BodyParser extends DebuggableService {
|
|
|
1621
1630
|
mediaType
|
|
1622
1631
|
);
|
|
1623
1632
|
}
|
|
1624
|
-
const parser = this._parsers[mediaType];
|
|
1633
|
+
const parser = this._parsers[mediaType.toLowerCase()];
|
|
1625
1634
|
if (!parser) {
|
|
1626
1635
|
throw new import_js_format15.InvalidArgumentError(
|
|
1627
1636
|
"Media type %v does not have a parser.",
|
|
@@ -1643,7 +1652,7 @@ var _BodyParser = class _BodyParser extends DebuggableService {
|
|
|
1643
1652
|
mediaType
|
|
1644
1653
|
);
|
|
1645
1654
|
}
|
|
1646
|
-
delete this._parsers[mediaType];
|
|
1655
|
+
delete this._parsers[mediaType.toLowerCase()];
|
|
1647
1656
|
return this;
|
|
1648
1657
|
}
|
|
1649
1658
|
/**
|
|
@@ -1659,17 +1668,11 @@ var _BodyParser = class _BodyParser extends DebuggableService {
|
|
|
1659
1668
|
request.method.toUpperCase(),
|
|
1660
1669
|
getRequestPathname(request)
|
|
1661
1670
|
);
|
|
1662
|
-
if (!
|
|
1663
|
-
debug(
|
|
1664
|
-
"Skipping body parsing for the %s method.",
|
|
1665
|
-
request.method.toUpperCase()
|
|
1666
|
-
);
|
|
1671
|
+
if (!hasRequestBody(request)) {
|
|
1672
|
+
debug("Skipping body parsing because no body is provided.");
|
|
1667
1673
|
return;
|
|
1668
1674
|
}
|
|
1669
|
-
const contentType =
|
|
1670
|
-
/^([^;]+);.*$/,
|
|
1671
|
-
"$1"
|
|
1672
|
-
);
|
|
1675
|
+
const contentType = request.headers["content-type"];
|
|
1673
1676
|
if (!contentType) {
|
|
1674
1677
|
debug("Skipping body parsing because no content type is provided.");
|
|
1675
1678
|
return;
|
|
@@ -1681,17 +1684,10 @@ var _BodyParser = class _BodyParser extends DebuggableService {
|
|
|
1681
1684
|
'Unable to parse the "content-type" header.'
|
|
1682
1685
|
);
|
|
1683
1686
|
}
|
|
1684
|
-
const parser = this._parsers[mediaType];
|
|
1687
|
+
const parser = this._parsers[mediaType.toLowerCase()];
|
|
1685
1688
|
if (!parser) {
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
return;
|
|
1689
|
-
}
|
|
1690
|
-
throw createError(
|
|
1691
|
-
import_http_errors2.default.UnsupportedMediaType,
|
|
1692
|
-
"Media type %v is not supported.",
|
|
1693
|
-
mediaType
|
|
1694
|
-
);
|
|
1689
|
+
debug("No body parser for the media type %v.", mediaType);
|
|
1690
|
+
return;
|
|
1695
1691
|
}
|
|
1696
1692
|
const bodyBytesLimit = this.getService(RouterOptions).requestBodyBytesLimit;
|
|
1697
1693
|
debug("Fetching a request body.");
|
|
@@ -1715,7 +1711,7 @@ function parseJsonBody(input) {
|
|
|
1715
1711
|
try {
|
|
1716
1712
|
return JSON.parse(input);
|
|
1717
1713
|
} catch (error) {
|
|
1718
|
-
throw
|
|
1714
|
+
throw new import_http_errors2.default.BadRequest(error.message);
|
|
1719
1715
|
}
|
|
1720
1716
|
}
|
|
1721
1717
|
__name(parseJsonBody, "parseJsonBody");
|
|
@@ -2565,7 +2561,6 @@ var TrieRouter = _TrieRouter;
|
|
|
2565
2561
|
EXPOSED_ERROR_PROPERTIES,
|
|
2566
2562
|
ErrorSender,
|
|
2567
2563
|
HttpMethod,
|
|
2568
|
-
METHODS_WITH_BODY,
|
|
2569
2564
|
QueryParser,
|
|
2570
2565
|
ROOT_PATH,
|
|
2571
2566
|
ROUTER_HOOK_TYPES,
|
|
@@ -2578,7 +2573,6 @@ var TrieRouter = _TrieRouter;
|
|
|
2578
2573
|
RouterHookType,
|
|
2579
2574
|
RouterOptions,
|
|
2580
2575
|
TrieRouter,
|
|
2581
|
-
UNPARSABLE_MEDIA_TYPES,
|
|
2582
2576
|
cloneDeep,
|
|
2583
2577
|
createCookieString,
|
|
2584
2578
|
createError,
|
|
@@ -2587,6 +2581,7 @@ var TrieRouter = _TrieRouter;
|
|
|
2587
2581
|
createRouteMock,
|
|
2588
2582
|
fetchRequestBody,
|
|
2589
2583
|
getRequestPathname,
|
|
2584
|
+
hasRequestBody,
|
|
2590
2585
|
isPromise,
|
|
2591
2586
|
isReadableStream,
|
|
2592
2587
|
isResponseSent,
|
package/package.json
CHANGED
|
@@ -2,16 +2,6 @@ import {IncomingMessage} from 'http';
|
|
|
2
2
|
import {ValueOrPromise} from '../types.js';
|
|
3
3
|
import {DebuggableService} from '../debuggable-service.js';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Method names to be parsed.
|
|
7
|
-
*/
|
|
8
|
-
export declare const METHODS_WITH_BODY: string[];
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Unparsable media types.
|
|
12
|
-
*/
|
|
13
|
-
export declare const UNPARSABLE_MEDIA_TYPES: string[];
|
|
14
|
-
|
|
15
5
|
/**
|
|
16
6
|
* Body parser function.
|
|
17
7
|
*/
|
|
@@ -5,25 +5,12 @@ import {DebuggableService} from '../debuggable-service.js';
|
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
createError,
|
|
8
|
+
hasRequestBody,
|
|
8
9
|
parseContentType,
|
|
9
10
|
fetchRequestBody,
|
|
10
11
|
getRequestPathname,
|
|
11
12
|
} from '../utils/index.js';
|
|
12
13
|
|
|
13
|
-
/**
|
|
14
|
-
* Method names to be parsed.
|
|
15
|
-
*
|
|
16
|
-
* @type {string[]}
|
|
17
|
-
*/
|
|
18
|
-
export const METHODS_WITH_BODY = ['POST', 'PUT', 'PATCH', 'DELETE'];
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Unparsable media types.
|
|
22
|
-
*
|
|
23
|
-
* @type {string[]}
|
|
24
|
-
*/
|
|
25
|
-
export const UNPARSABLE_MEDIA_TYPES = ['multipart/form-data'];
|
|
26
|
-
|
|
27
14
|
/**
|
|
28
15
|
* Body parser.
|
|
29
16
|
*/
|
|
@@ -59,7 +46,7 @@ export class BodyParser extends DebuggableService {
|
|
|
59
46
|
parser,
|
|
60
47
|
);
|
|
61
48
|
}
|
|
62
|
-
this._parsers[mediaType] = parser;
|
|
49
|
+
this._parsers[mediaType.toLowerCase()] = parser;
|
|
63
50
|
return this;
|
|
64
51
|
}
|
|
65
52
|
|
|
@@ -77,7 +64,7 @@ export class BodyParser extends DebuggableService {
|
|
|
77
64
|
mediaType,
|
|
78
65
|
);
|
|
79
66
|
}
|
|
80
|
-
return Boolean(this._parsers[mediaType]);
|
|
67
|
+
return Boolean(this._parsers[mediaType.toLowerCase()]);
|
|
81
68
|
}
|
|
82
69
|
|
|
83
70
|
/**
|
|
@@ -94,7 +81,7 @@ export class BodyParser extends DebuggableService {
|
|
|
94
81
|
mediaType,
|
|
95
82
|
);
|
|
96
83
|
}
|
|
97
|
-
const parser = this._parsers[mediaType];
|
|
84
|
+
const parser = this._parsers[mediaType.toLowerCase()];
|
|
98
85
|
if (!parser) {
|
|
99
86
|
throw new InvalidArgumentError(
|
|
100
87
|
'Media type %v does not have a parser.',
|
|
@@ -118,7 +105,7 @@ export class BodyParser extends DebuggableService {
|
|
|
118
105
|
mediaType,
|
|
119
106
|
);
|
|
120
107
|
}
|
|
121
|
-
delete this._parsers[mediaType];
|
|
108
|
+
delete this._parsers[mediaType.toLowerCase()];
|
|
122
109
|
return this;
|
|
123
110
|
}
|
|
124
111
|
|
|
@@ -135,17 +122,11 @@ export class BodyParser extends DebuggableService {
|
|
|
135
122
|
request.method.toUpperCase(),
|
|
136
123
|
getRequestPathname(request),
|
|
137
124
|
);
|
|
138
|
-
if (!
|
|
139
|
-
debug(
|
|
140
|
-
'Skipping body parsing for the %s method.',
|
|
141
|
-
request.method.toUpperCase(),
|
|
142
|
-
);
|
|
125
|
+
if (!hasRequestBody(request)) {
|
|
126
|
+
debug('Skipping body parsing because no body is provided.');
|
|
143
127
|
return;
|
|
144
128
|
}
|
|
145
|
-
const contentType =
|
|
146
|
-
/^([^;]+);.*$/,
|
|
147
|
-
'$1',
|
|
148
|
-
);
|
|
129
|
+
const contentType = request.headers['content-type'];
|
|
149
130
|
if (!contentType) {
|
|
150
131
|
debug('Skipping body parsing because no content type is provided.');
|
|
151
132
|
return;
|
|
@@ -157,17 +138,10 @@ export class BodyParser extends DebuggableService {
|
|
|
157
138
|
'Unable to parse the "content-type" header.',
|
|
158
139
|
);
|
|
159
140
|
}
|
|
160
|
-
const parser = this._parsers[mediaType];
|
|
141
|
+
const parser = this._parsers[mediaType.toLowerCase()];
|
|
161
142
|
if (!parser) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
throw createError(
|
|
167
|
-
HttpErrors.UnsupportedMediaType,
|
|
168
|
-
'Media type %v is not supported.',
|
|
169
|
-
mediaType,
|
|
170
|
-
);
|
|
143
|
+
debug('No body parser for the media type %v.', mediaType);
|
|
144
|
+
return;
|
|
171
145
|
}
|
|
172
146
|
const bodyBytesLimit = this.getService(RouterOptions).requestBodyBytesLimit;
|
|
173
147
|
debug('Fetching a request body.');
|
|
@@ -196,6 +170,6 @@ export function parseJsonBody(input) {
|
|
|
196
170
|
try {
|
|
197
171
|
return JSON.parse(input);
|
|
198
172
|
} catch (error) {
|
|
199
|
-
throw
|
|
173
|
+
throw new HttpErrors.BadRequest(error.message);
|
|
200
174
|
}
|
|
201
175
|
}
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import {expect} from 'chai';
|
|
2
2
|
import HttpErrors from 'http-errors';
|
|
3
3
|
import {format} from '@e22m4u/js-format';
|
|
4
|
+
import {BodyParser} from './body-parser.js';
|
|
4
5
|
import {HttpMethod} from '../route/index.js';
|
|
5
6
|
import {RouterOptions} from '../router-options.js';
|
|
6
7
|
import {createRequestMock} from '../utils/index.js';
|
|
7
8
|
|
|
8
|
-
import {
|
|
9
|
-
BodyParser,
|
|
10
|
-
METHODS_WITH_BODY,
|
|
11
|
-
UNPARSABLE_MEDIA_TYPES,
|
|
12
|
-
} from './body-parser.js';
|
|
13
|
-
|
|
14
9
|
describe('BodyParser', function () {
|
|
15
10
|
describe('defineParser', function () {
|
|
16
11
|
it('should require the parameter "mediaType" to be a non-empty String', function () {
|
|
@@ -106,6 +101,13 @@ describe('BodyParser', function () {
|
|
|
106
101
|
S.defineParser(mediaType, parser);
|
|
107
102
|
expect(S.hasParser(mediaType)).to.be.true;
|
|
108
103
|
});
|
|
104
|
+
|
|
105
|
+
it('should be case-insensitive when looking up the parser', function () {
|
|
106
|
+
const S = new BodyParser();
|
|
107
|
+
const parser = v => v;
|
|
108
|
+
S.defineParser('MeDiA/TyPe', parser);
|
|
109
|
+
expect(S.hasParser('mEdIa/tYpE')).to.be.true;
|
|
110
|
+
});
|
|
109
111
|
});
|
|
110
112
|
|
|
111
113
|
describe('getParser', function () {
|
|
@@ -147,6 +149,13 @@ describe('BodyParser', function () {
|
|
|
147
149
|
S.defineParser(mediaType, parser);
|
|
148
150
|
expect(S.getParser(mediaType)).to.be.eq(parser);
|
|
149
151
|
});
|
|
152
|
+
|
|
153
|
+
it('should be case-insensitive when looking up the parser', function () {
|
|
154
|
+
const S = new BodyParser();
|
|
155
|
+
const parser = v => v;
|
|
156
|
+
S.defineParser('MeDiA/TyPe', parser);
|
|
157
|
+
expect(S.getParser('mEdIa/tYpE')).to.be.eq(parser);
|
|
158
|
+
});
|
|
150
159
|
});
|
|
151
160
|
|
|
152
161
|
describe('removeParser', function () {
|
|
@@ -188,71 +197,77 @@ describe('BodyParser', function () {
|
|
|
188
197
|
S.removeParser(mediaType);
|
|
189
198
|
expect(S.hasParser(mediaType)).to.be.false;
|
|
190
199
|
});
|
|
191
|
-
});
|
|
192
200
|
|
|
193
|
-
|
|
194
|
-
it('should return undefined when the request method is not supported', async function () {
|
|
201
|
+
it('should be case-insensitive when removing the parser', function () {
|
|
195
202
|
const S = new BodyParser();
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
expect(
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('should return undefined when the request method is not supported even if the "content-type" header is specified', async function () {
|
|
205
|
-
const S = new BodyParser();
|
|
206
|
-
const req = createRequestMock({
|
|
207
|
-
method: 'unsupported',
|
|
208
|
-
headers: {'content-type': 'text/plain'},
|
|
209
|
-
body: 'Lorem Ipsum is simply dummy text.',
|
|
210
|
-
});
|
|
211
|
-
const result = await S.parse(req);
|
|
212
|
-
expect(result).to.be.undefined;
|
|
203
|
+
const parser = v => v;
|
|
204
|
+
const mediaType = 'MeDiA/TyPe';
|
|
205
|
+
S.defineParser(mediaType, parser);
|
|
206
|
+
expect(S.hasParser(mediaType)).to.be.true;
|
|
207
|
+
S.removeParser('mEdIa/tYpE');
|
|
208
|
+
expect(S.hasParser(mediaType)).to.be.false;
|
|
213
209
|
});
|
|
210
|
+
});
|
|
214
211
|
|
|
215
|
-
|
|
212
|
+
describe('parse', function () {
|
|
213
|
+
it('should parse the request body when the "content-type" and "content-length" headers are provided', async function () {
|
|
216
214
|
const S = new BodyParser();
|
|
217
|
-
const
|
|
218
|
-
const
|
|
219
|
-
|
|
215
|
+
const body = 'Lorem Ipsum is simply dummy text.';
|
|
216
|
+
const headers = {
|
|
217
|
+
'content-type': 'text/plain',
|
|
218
|
+
'content-length': Buffer.byteLength(body, 'utf-8'),
|
|
219
|
+
};
|
|
220
|
+
for await (const method of Object.values(HttpMethod)) {
|
|
221
|
+
const req = createRequestMock({method, body, headers});
|
|
222
|
+
const result = await S.parse(req);
|
|
223
|
+
expect(result).to.be.eq(body);
|
|
224
|
+
}
|
|
220
225
|
});
|
|
221
226
|
|
|
222
|
-
it('should
|
|
227
|
+
it('should parse the request body when the "content-type" and "transfer-encoding" headers are provided', async function () {
|
|
223
228
|
const S = new BodyParser();
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
229
|
+
const body = 'Lorem Ipsum is simply dummy text.';
|
|
230
|
+
const headers = {
|
|
231
|
+
'content-type': 'text/plain',
|
|
232
|
+
'transfer-encoding': 'chunked',
|
|
233
|
+
};
|
|
234
|
+
for await (const method of Object.values(HttpMethod)) {
|
|
235
|
+
const req = createRequestMock({method, body, headers});
|
|
230
236
|
const result = await S.parse(req);
|
|
231
|
-
expect(result).to.be.
|
|
237
|
+
expect(result).to.be.eq(body);
|
|
232
238
|
}
|
|
233
239
|
});
|
|
234
240
|
|
|
235
|
-
it('should
|
|
241
|
+
it('should skip parsing when the header "content-length" has an invalid value', async function () {
|
|
236
242
|
const S = new BodyParser();
|
|
237
243
|
const body = 'Lorem Ipsum is simply dummy text.';
|
|
238
|
-
const headers = {
|
|
239
|
-
|
|
244
|
+
const headers = {
|
|
245
|
+
'content-type': 'text/plain',
|
|
246
|
+
'content-length': 'invalid',
|
|
247
|
+
};
|
|
248
|
+
for await (const method of Object.values(HttpMethod)) {
|
|
240
249
|
const req = createRequestMock({method, body, headers});
|
|
241
250
|
const result = await S.parse(req);
|
|
242
|
-
expect(result).to.be.
|
|
251
|
+
expect(result).to.be.undefined;
|
|
243
252
|
}
|
|
244
253
|
});
|
|
245
254
|
|
|
246
|
-
it('should
|
|
255
|
+
it('should return undefined when no "content-type" header is provided', async function () {
|
|
256
|
+
const S = new BodyParser();
|
|
257
|
+
const req = createRequestMock({method: HttpMethod.POST});
|
|
258
|
+
const result = await S.parse(req);
|
|
259
|
+
expect(result).to.be.undefined;
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should return undefined when the media type does not have a registered parser', async function () {
|
|
247
263
|
const S = new BodyParser();
|
|
248
264
|
const req = createRequestMock({
|
|
249
265
|
method: HttpMethod.POST,
|
|
250
|
-
headers: {'content-type': '
|
|
266
|
+
headers: {'content-type': 'type/unknown'},
|
|
267
|
+
body: 'Lorem Ipsum is simply dummy text.',
|
|
251
268
|
});
|
|
252
|
-
const
|
|
253
|
-
expect(
|
|
254
|
-
'Media type "media/unknown" is not supported.',
|
|
255
|
-
);
|
|
269
|
+
const result = await S.parse(req);
|
|
270
|
+
expect(result).to.be.undefined;
|
|
256
271
|
});
|
|
257
272
|
|
|
258
273
|
it('should use the option "bodyBytesLimit" from the RouterOptions', async function () {
|