@e22m4u/js-trie-router 0.7.1 → 0.7.3
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/cjs/index.cjs +27 -73
- package/package.json +1 -1
- package/src/parsers/request-body-parser.js +4 -3
- package/src/trie-router-options.d.ts +7 -21
- package/src/trie-router-options.js +19 -71
- package/src/trie-router-options.spec.js +10 -189
- package/src/trie-router.spec.js +2 -2
- package/src/utils/create-request-mock.d.ts +1 -8
- package/src/utils/create-request-mock.js +13 -6
- package/src/utils/create-request-mock.spec.js +80 -13
package/dist/cjs/index.cjs
CHANGED
|
@@ -544,9 +544,9 @@ function createRequestMock(options) {
|
|
|
544
544
|
}
|
|
545
545
|
}
|
|
546
546
|
if (options.query !== void 0) {
|
|
547
|
-
if (
|
|
547
|
+
if (options.query === null || typeof options.query !== "string" && typeof options.query !== "object" || Array.isArray(options.query)) {
|
|
548
548
|
throw new import_js_format9.InvalidArgumentError(
|
|
549
|
-
'Option "query" must be an Object, but %v was given.',
|
|
549
|
+
'Option "query" must be a String or an Object, but %v was given.',
|
|
550
550
|
options.query
|
|
551
551
|
);
|
|
552
552
|
}
|
|
@@ -721,9 +721,9 @@ function createRequestUrl(path, query) {
|
|
|
721
721
|
);
|
|
722
722
|
}
|
|
723
723
|
if (query !== void 0) {
|
|
724
|
-
if (
|
|
724
|
+
if (query === null || typeof query !== "string" && typeof query !== "object" || Array.isArray(query)) {
|
|
725
725
|
throw new import_js_format9.InvalidArgumentError(
|
|
726
|
-
'Parameter "query" must be an Object, but %v was given.',
|
|
726
|
+
'Parameter "query" must be a String or an Object, but %v was given.',
|
|
727
727
|
query
|
|
728
728
|
);
|
|
729
729
|
}
|
|
@@ -734,6 +734,8 @@ function createRequestUrl(path, query) {
|
|
|
734
734
|
if (qs) {
|
|
735
735
|
res += `?${qs}`;
|
|
736
736
|
}
|
|
737
|
+
} else if (typeof query === "string" && query !== "" && query !== "?") {
|
|
738
|
+
res += `?${query.replace(/^\?/, "")}`;
|
|
737
739
|
}
|
|
738
740
|
return res;
|
|
739
741
|
}
|
|
@@ -2069,12 +2071,28 @@ var _TrieRouterOptions = class _TrieRouterOptions {
|
|
|
2069
2071
|
*/
|
|
2070
2072
|
_requestBodyBytesLimit = 512 * 1024;
|
|
2071
2073
|
// 512kb
|
|
2074
|
+
/**
|
|
2075
|
+
* Request body bytes limit.
|
|
2076
|
+
*
|
|
2077
|
+
* @type {number}
|
|
2078
|
+
*/
|
|
2079
|
+
get requestBodyBytesLimit() {
|
|
2080
|
+
return this._requestBodyBytesLimit;
|
|
2081
|
+
}
|
|
2072
2082
|
/**
|
|
2073
2083
|
* Ignored media types.
|
|
2074
2084
|
*
|
|
2075
2085
|
* @type {string[]}
|
|
2076
2086
|
*/
|
|
2077
2087
|
_ignoredMediaTypes = [];
|
|
2088
|
+
/**
|
|
2089
|
+
* Ignored media types.
|
|
2090
|
+
*
|
|
2091
|
+
* @type {string[]}
|
|
2092
|
+
*/
|
|
2093
|
+
get ignoredMediaTypes() {
|
|
2094
|
+
return this._ignoredMediaTypes;
|
|
2095
|
+
}
|
|
2078
2096
|
/**
|
|
2079
2097
|
* Constructor.
|
|
2080
2098
|
*
|
|
@@ -2116,74 +2134,9 @@ var _TrieRouterOptions = class _TrieRouterOptions {
|
|
|
2116
2134
|
this._ignoredMediaTypes.push(mediaTypeLc);
|
|
2117
2135
|
}
|
|
2118
2136
|
});
|
|
2137
|
+
Object.freeze(this._ignoredMediaTypes);
|
|
2119
2138
|
}
|
|
2120
2139
|
}
|
|
2121
|
-
/**
|
|
2122
|
-
* Get request body bytes limit.
|
|
2123
|
-
*
|
|
2124
|
-
* @param {number} limit
|
|
2125
|
-
* @returns {this}
|
|
2126
|
-
*/
|
|
2127
|
-
setRequestBodyBytesLimit(limit) {
|
|
2128
|
-
if (typeof limit !== "number" || limit < 0) {
|
|
2129
|
-
throw new import_js_format18.InvalidArgumentError(
|
|
2130
|
-
'Parameter "limit" must be a positive Number or 0, but %v was given.',
|
|
2131
|
-
limit
|
|
2132
|
-
);
|
|
2133
|
-
}
|
|
2134
|
-
this._requestBodyBytesLimit = limit;
|
|
2135
|
-
return this;
|
|
2136
|
-
}
|
|
2137
|
-
/**
|
|
2138
|
-
* Get request body bytes limit.
|
|
2139
|
-
*
|
|
2140
|
-
* @returns {number}
|
|
2141
|
-
*/
|
|
2142
|
-
getRequestBodyBytesLimit() {
|
|
2143
|
-
return this._requestBodyBytesLimit;
|
|
2144
|
-
}
|
|
2145
|
-
/**
|
|
2146
|
-
* Get ignored media types.
|
|
2147
|
-
*
|
|
2148
|
-
* @param {string} mediaType
|
|
2149
|
-
* @returns {this}
|
|
2150
|
-
*/
|
|
2151
|
-
addIgnoredMediaType(mediaType) {
|
|
2152
|
-
if (!mediaType || typeof mediaType !== "string") {
|
|
2153
|
-
throw new import_js_format18.InvalidArgumentError(
|
|
2154
|
-
'Parameter "mediaType" must be a non-empty String, but %v was given.',
|
|
2155
|
-
mediaType
|
|
2156
|
-
);
|
|
2157
|
-
}
|
|
2158
|
-
const mediaTypeLc = mediaType.toLowerCase();
|
|
2159
|
-
if (!this._ignoredMediaTypes.includes(mediaTypeLc)) {
|
|
2160
|
-
this._ignoredMediaTypes.push(mediaTypeLc);
|
|
2161
|
-
}
|
|
2162
|
-
return this;
|
|
2163
|
-
}
|
|
2164
|
-
/**
|
|
2165
|
-
* Has ignored media type.
|
|
2166
|
-
*
|
|
2167
|
-
* @param {string} mediaType
|
|
2168
|
-
* @returns {boolean}
|
|
2169
|
-
*/
|
|
2170
|
-
hasIgnoredMediaType(mediaType) {
|
|
2171
|
-
if (!mediaType || typeof mediaType !== "string") {
|
|
2172
|
-
throw new import_js_format18.InvalidArgumentError(
|
|
2173
|
-
'Parameter "mediaType" must be a non-empty String, but %v was given.',
|
|
2174
|
-
mediaType
|
|
2175
|
-
);
|
|
2176
|
-
}
|
|
2177
|
-
return this._ignoredMediaTypes.includes(mediaType.toLowerCase());
|
|
2178
|
-
}
|
|
2179
|
-
/**
|
|
2180
|
-
* Get ignored media types.
|
|
2181
|
-
*
|
|
2182
|
-
* @returns {string[]}
|
|
2183
|
-
*/
|
|
2184
|
-
getIgnoredMediaTypes() {
|
|
2185
|
-
return this._ignoredMediaTypes.slice();
|
|
2186
|
-
}
|
|
2187
2140
|
};
|
|
2188
2141
|
__name(_TrieRouterOptions, "TrieRouterOptions");
|
|
2189
2142
|
var TrieRouterOptions = _TrieRouterOptions;
|
|
@@ -2305,12 +2258,13 @@ var _RequestBodyParser = class _RequestBodyParser extends DebuggableService {
|
|
|
2305
2258
|
);
|
|
2306
2259
|
}
|
|
2307
2260
|
const options = this.getService(TrieRouterOptions);
|
|
2308
|
-
const
|
|
2261
|
+
const mediaTypeLc = mediaType.toLowerCase();
|
|
2262
|
+
const isMediaTypeIgnored = options.ignoredMediaTypes.includes(mediaTypeLc);
|
|
2309
2263
|
if (isMediaTypeIgnored) {
|
|
2310
2264
|
debug("Media type %v is ignored.", mediaType);
|
|
2311
2265
|
return;
|
|
2312
2266
|
}
|
|
2313
|
-
const parser = this._parsers[
|
|
2267
|
+
const parser = this._parsers[mediaTypeLc];
|
|
2314
2268
|
if (!parser) {
|
|
2315
2269
|
throw createError(
|
|
2316
2270
|
import_http_errors2.default.UnsupportedMediaType,
|
|
@@ -2318,7 +2272,7 @@ var _RequestBodyParser = class _RequestBodyParser extends DebuggableService {
|
|
|
2318
2272
|
mediaType
|
|
2319
2273
|
);
|
|
2320
2274
|
}
|
|
2321
|
-
const bodyBytesLimit = options.
|
|
2275
|
+
const bodyBytesLimit = options.requestBodyBytesLimit;
|
|
2322
2276
|
debug("Fetching a request body.");
|
|
2323
2277
|
debug("Body limit is %v bytes.", bodyBytesLimit);
|
|
2324
2278
|
return fetchRequestBody(request, bodyBytesLimit).then((rawBody) => {
|
package/package.json
CHANGED
|
@@ -147,14 +147,15 @@ export class RequestBodyParser extends DebuggableService {
|
|
|
147
147
|
// если текущий медиа тип исключен
|
|
148
148
|
// настройками, то парсинг пропускается
|
|
149
149
|
const options = this.getService(TrieRouterOptions);
|
|
150
|
-
const
|
|
150
|
+
const mediaTypeLc = mediaType.toLowerCase();
|
|
151
|
+
const isMediaTypeIgnored = options.ignoredMediaTypes.includes(mediaTypeLc);
|
|
151
152
|
if (isMediaTypeIgnored) {
|
|
152
153
|
debug('Media type %v is ignored.', mediaType);
|
|
153
154
|
return;
|
|
154
155
|
}
|
|
155
156
|
// если парсер для текущего медиа типа
|
|
156
157
|
// не определен, то выбрасывается ошибка
|
|
157
|
-
const parser = this._parsers[
|
|
158
|
+
const parser = this._parsers[mediaTypeLc];
|
|
158
159
|
if (!parser) {
|
|
159
160
|
throw createError(
|
|
160
161
|
HttpErrors.UnsupportedMediaType,
|
|
@@ -164,7 +165,7 @@ export class RequestBodyParser extends DebuggableService {
|
|
|
164
165
|
}
|
|
165
166
|
// определение максимального количества
|
|
166
167
|
// байт, извлекаемых из тела запроса
|
|
167
|
-
const bodyBytesLimit = options.
|
|
168
|
+
const bodyBytesLimit = options.requestBodyBytesLimit;
|
|
168
169
|
debug('Fetching a request body.');
|
|
169
170
|
debug('Body limit is %v bytes.', bodyBytesLimit);
|
|
170
171
|
// извлечение тела запроса для последующего
|
|
@@ -11,33 +11,19 @@ export type TrieRouterOptionsInput = {
|
|
|
11
11
|
*/
|
|
12
12
|
export declare class TrieRouterOptions {
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* @param limit
|
|
17
|
-
*/
|
|
18
|
-
setRequestBodyBytesLimit(limit: number): this;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Get request body bytes limit.
|
|
14
|
+
* Request body bytes limit.
|
|
22
15
|
*/
|
|
23
|
-
|
|
16
|
+
get requestBodyBytesLimit(): number;
|
|
24
17
|
|
|
25
18
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* @param mediaType
|
|
19
|
+
* Request body bytes limit.
|
|
29
20
|
*/
|
|
30
|
-
|
|
21
|
+
get ignoredMediaTypes(): string[];
|
|
31
22
|
|
|
32
23
|
/**
|
|
33
|
-
*
|
|
24
|
+
* Constructor.
|
|
34
25
|
*
|
|
35
|
-
* @param
|
|
36
|
-
*/
|
|
37
|
-
hasIgnoredMediaType(mediaType: string): boolean;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Get ignored media types.
|
|
26
|
+
* @param options
|
|
41
27
|
*/
|
|
42
|
-
|
|
28
|
+
constructor(options?: TrieRouterOptionsInput);
|
|
43
29
|
}
|
|
@@ -11,6 +11,15 @@ export class TrieRouterOptions {
|
|
|
11
11
|
*/
|
|
12
12
|
_requestBodyBytesLimit = 512 * 1024; // 512kb
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Request body bytes limit.
|
|
16
|
+
*
|
|
17
|
+
* @type {number}
|
|
18
|
+
*/
|
|
19
|
+
get requestBodyBytesLimit() {
|
|
20
|
+
return this._requestBodyBytesLimit;
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
/**
|
|
15
24
|
* Ignored media types.
|
|
16
25
|
*
|
|
@@ -18,6 +27,15 @@ export class TrieRouterOptions {
|
|
|
18
27
|
*/
|
|
19
28
|
_ignoredMediaTypes = [];
|
|
20
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Ignored media types.
|
|
32
|
+
*
|
|
33
|
+
* @type {string[]}
|
|
34
|
+
*/
|
|
35
|
+
get ignoredMediaTypes() {
|
|
36
|
+
return this._ignoredMediaTypes;
|
|
37
|
+
}
|
|
38
|
+
|
|
21
39
|
/**
|
|
22
40
|
* Constructor.
|
|
23
41
|
*
|
|
@@ -66,77 +84,7 @@ export class TrieRouterOptions {
|
|
|
66
84
|
this._ignoredMediaTypes.push(mediaTypeLc);
|
|
67
85
|
}
|
|
68
86
|
});
|
|
87
|
+
Object.freeze(this._ignoredMediaTypes);
|
|
69
88
|
}
|
|
70
89
|
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Get request body bytes limit.
|
|
74
|
-
*
|
|
75
|
-
* @param {number} limit
|
|
76
|
-
* @returns {this}
|
|
77
|
-
*/
|
|
78
|
-
setRequestBodyBytesLimit(limit) {
|
|
79
|
-
if (typeof limit !== 'number' || limit < 0) {
|
|
80
|
-
throw new InvalidArgumentError(
|
|
81
|
-
'Parameter "limit" must be a positive Number or 0, but %v was given.',
|
|
82
|
-
limit,
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
this._requestBodyBytesLimit = limit;
|
|
86
|
-
return this;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Get request body bytes limit.
|
|
91
|
-
*
|
|
92
|
-
* @returns {number}
|
|
93
|
-
*/
|
|
94
|
-
getRequestBodyBytesLimit() {
|
|
95
|
-
return this._requestBodyBytesLimit;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Get ignored media types.
|
|
100
|
-
*
|
|
101
|
-
* @param {string} mediaType
|
|
102
|
-
* @returns {this}
|
|
103
|
-
*/
|
|
104
|
-
addIgnoredMediaType(mediaType) {
|
|
105
|
-
if (!mediaType || typeof mediaType !== 'string') {
|
|
106
|
-
throw new InvalidArgumentError(
|
|
107
|
-
'Parameter "mediaType" must be a non-empty String, but %v was given.',
|
|
108
|
-
mediaType,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
const mediaTypeLc = mediaType.toLowerCase();
|
|
112
|
-
if (!this._ignoredMediaTypes.includes(mediaTypeLc)) {
|
|
113
|
-
this._ignoredMediaTypes.push(mediaTypeLc);
|
|
114
|
-
}
|
|
115
|
-
return this;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Has ignored media type.
|
|
120
|
-
*
|
|
121
|
-
* @param {string} mediaType
|
|
122
|
-
* @returns {boolean}
|
|
123
|
-
*/
|
|
124
|
-
hasIgnoredMediaType(mediaType) {
|
|
125
|
-
if (!mediaType || typeof mediaType !== 'string') {
|
|
126
|
-
throw new InvalidArgumentError(
|
|
127
|
-
'Parameter "mediaType" must be a non-empty String, but %v was given.',
|
|
128
|
-
mediaType,
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
return this._ignoredMediaTypes.includes(mediaType.toLowerCase());
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Get ignored media types.
|
|
136
|
-
*
|
|
137
|
-
* @returns {string[]}
|
|
138
|
-
*/
|
|
139
|
-
getIgnoredMediaTypes() {
|
|
140
|
-
return this._ignoredMediaTypes.slice();
|
|
141
|
-
}
|
|
142
90
|
}
|
|
@@ -87,210 +87,31 @@ describe('TrieRouterOptions', function () {
|
|
|
87
87
|
it('should set the option "requestBodyBytesLimit" to the current instance', function () {
|
|
88
88
|
const value = 10;
|
|
89
89
|
const inst = new TrieRouterOptions({requestBodyBytesLimit: value});
|
|
90
|
-
expect(inst.
|
|
90
|
+
expect(inst.requestBodyBytesLimit).to.be.eq(value);
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
it('should add elements of the option "ignoredMediaTypes" to the current instance', function () {
|
|
94
94
|
const value = ['text/plain', 'text/html'];
|
|
95
95
|
const inst = new TrieRouterOptions({ignoredMediaTypes: value});
|
|
96
|
-
expect(inst.
|
|
97
|
-
expect(inst.getIgnoredMediaTypes()).to.include(value[1]);
|
|
96
|
+
expect(inst.ignoredMediaTypes).to.be.eql(['text/plain', 'text/html']);
|
|
98
97
|
});
|
|
99
98
|
|
|
100
99
|
it('should add elements of the option "ignoredMediaTypes" without duplicates', function () {
|
|
101
100
|
const value = ['text/plain', 'text/html', 'text/plain'];
|
|
102
101
|
const inst = new TrieRouterOptions({ignoredMediaTypes: value});
|
|
103
|
-
expect(inst.
|
|
104
|
-
'text/plain',
|
|
105
|
-
'text/html',
|
|
106
|
-
]);
|
|
102
|
+
expect(inst.ignoredMediaTypes).to.be.eql(['text/plain', 'text/html']);
|
|
107
103
|
});
|
|
108
104
|
|
|
109
|
-
it('should
|
|
110
|
-
const inst = new TrieRouterOptions({ignoredMediaTypes: ['TEXT/PLAIN']});
|
|
111
|
-
expect(inst.getIgnoredMediaTypes()).to.be.eql(['text/plain']);
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
describe('setRequestBodyBytesLimit', function () {
|
|
116
|
-
it('should require the parameter "value" to be a positive number or zero', function () {
|
|
117
|
-
const throwable = v => () => {
|
|
118
|
-
const S = new TrieRouterOptions();
|
|
119
|
-
S.setRequestBodyBytesLimit(v);
|
|
120
|
-
};
|
|
121
|
-
const error = s =>
|
|
122
|
-
format(
|
|
123
|
-
'Parameter "limit" must be a positive Number or 0, but %s was given.',
|
|
124
|
-
s,
|
|
125
|
-
);
|
|
126
|
-
expect(throwable('str')).to.throw(error('"str"'));
|
|
127
|
-
expect(throwable('')).to.throw(error('""'));
|
|
128
|
-
expect(throwable(-1)).to.throw(error('-1'));
|
|
129
|
-
expect(throwable(true)).to.throw(error('true'));
|
|
130
|
-
expect(throwable(false)).to.throw(error('false'));
|
|
131
|
-
expect(throwable([])).to.throw(error('Array'));
|
|
132
|
-
expect(throwable({})).to.throw(error('Object'));
|
|
133
|
-
expect(throwable(undefined)).to.throw(error('undefined'));
|
|
134
|
-
expect(throwable(null)).to.throw(error('null'));
|
|
135
|
-
throwable(10)();
|
|
136
|
-
throwable(0)();
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
it('should set the option value to the current instance', function () {
|
|
140
|
-
const S = new TrieRouterOptions();
|
|
141
|
-
const customValue = 10;
|
|
142
|
-
S.setRequestBodyBytesLimit(customValue);
|
|
143
|
-
const res = S.getRequestBodyBytesLimit();
|
|
144
|
-
expect(res).to.be.eq(customValue);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('should return the current instance to allow chaining', function () {
|
|
148
|
-
const S = new TrieRouterOptions();
|
|
149
|
-
const res = S.setRequestBodyBytesLimit(10);
|
|
150
|
-
expect(res).to.be.eq(S);
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
describe('getRequestBodyBytesLimit', function () {
|
|
155
|
-
it('should return a default value when the option is not specified', function () {
|
|
156
|
-
const defaultValue = 512 * 1024; // 512kb
|
|
157
|
-
const inst = new TrieRouterOptions();
|
|
158
|
-
expect(inst.getRequestBodyBytesLimit()).to.be.eq(defaultValue);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('should return a value specified in the constructor', function () {
|
|
162
|
-
const customValue = 10;
|
|
163
|
-
const inst = new TrieRouterOptions({requestBodyBytesLimit: customValue});
|
|
164
|
-
expect(inst.getRequestBodyBytesLimit()).to.be.eq(customValue);
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
describe('addIgnoredMediaType', function () {
|
|
169
|
-
it('should require the parameter "mediaType" to be a non-empty String', function () {
|
|
170
|
-
const throwable = v => () => {
|
|
171
|
-
const S = new TrieRouterOptions();
|
|
172
|
-
S.addIgnoredMediaType(v);
|
|
173
|
-
};
|
|
174
|
-
const error = s =>
|
|
175
|
-
format(
|
|
176
|
-
'Parameter "mediaType" must be a non-empty String, ' +
|
|
177
|
-
'but %s was given.',
|
|
178
|
-
s,
|
|
179
|
-
);
|
|
180
|
-
expect(throwable('')).to.throw(error('""'));
|
|
181
|
-
expect(throwable(10)).to.throw(error('10'));
|
|
182
|
-
expect(throwable(0)).to.throw(error('0'));
|
|
183
|
-
expect(throwable(true)).to.throw(error('true'));
|
|
184
|
-
expect(throwable(false)).to.throw(error('false'));
|
|
185
|
-
expect(throwable([])).to.throw(error('Array'));
|
|
186
|
-
expect(throwable({})).to.throw(error('Object'));
|
|
187
|
-
expect(throwable(undefined)).to.throw(error('undefined'));
|
|
188
|
-
expect(throwable(null)).to.throw(error('null'));
|
|
189
|
-
throwable('text/plain')();
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('should add an ignored media type to the current instance', function () {
|
|
193
|
-
const S = new TrieRouterOptions();
|
|
194
|
-
const mediaType = 'text/plain';
|
|
195
|
-
expect(S.hasIgnoredMediaType(mediaType)).to.be.false;
|
|
196
|
-
S.addIgnoredMediaType(mediaType);
|
|
197
|
-
expect(S.hasIgnoredMediaType(mediaType)).to.be.true;
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it('should return the current instance to allow chaining', function () {
|
|
201
|
-
const S = new TrieRouterOptions();
|
|
202
|
-
const res = S.addIgnoredMediaType('text/plain');
|
|
203
|
-
expect(res).to.be.eq(S);
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
describe('hasIgnoredMediaType', function () {
|
|
208
|
-
it('should require the parameter "mediaType" to be a non-empty String', function () {
|
|
209
|
-
const throwable = v => () => {
|
|
210
|
-
const S = new TrieRouterOptions();
|
|
211
|
-
S.hasIgnoredMediaType(v);
|
|
212
|
-
};
|
|
213
|
-
const error = s =>
|
|
214
|
-
format(
|
|
215
|
-
'Parameter "mediaType" must be a non-empty String, ' +
|
|
216
|
-
'but %s was given.',
|
|
217
|
-
s,
|
|
218
|
-
);
|
|
219
|
-
expect(throwable('')).to.throw(error('""'));
|
|
220
|
-
expect(throwable(10)).to.throw(error('10'));
|
|
221
|
-
expect(throwable(0)).to.throw(error('0'));
|
|
222
|
-
expect(throwable(true)).to.throw(error('true'));
|
|
223
|
-
expect(throwable(false)).to.throw(error('false'));
|
|
224
|
-
expect(throwable([])).to.throw(error('Array'));
|
|
225
|
-
expect(throwable({})).to.throw(error('Object'));
|
|
226
|
-
expect(throwable(undefined)).to.throw(error('undefined'));
|
|
227
|
-
expect(throwable(null)).to.throw(error('null'));
|
|
228
|
-
throwable('text/plain')();
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('should return true when the given type is registered', function () {
|
|
232
|
-
const S = new TrieRouterOptions();
|
|
233
|
-
const mediaType = 'text/plain';
|
|
234
|
-
expect(S.hasIgnoredMediaType(mediaType)).to.be.false;
|
|
235
|
-
S.addIgnoredMediaType(mediaType);
|
|
236
|
-
expect(S.hasIgnoredMediaType(mediaType)).to.be.true;
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it('should lookup the media type in case-insensitive mode', function () {
|
|
240
|
-
const S = new TrieRouterOptions();
|
|
241
|
-
S.addIgnoredMediaType('TeXt/PlAiN');
|
|
242
|
-
expect(S.hasIgnoredMediaType('tExT/pLaIn')).to.be.true;
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it('should not add duplicates even with different cases', function () {
|
|
246
|
-
const S = new TrieRouterOptions();
|
|
247
|
-
S.addIgnoredMediaType('text/plain');
|
|
248
|
-
S.addIgnoredMediaType('TEXT/PLAIN');
|
|
249
|
-
S.addIgnoredMediaType('text/plain');
|
|
250
|
-
const res = S.getIgnoredMediaTypes();
|
|
251
|
-
expect(res).to.have.lengthOf(1);
|
|
252
|
-
expect(res[0]).to.be.eq('text/plain');
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
describe('getIgnoredMediaTypes', function () {
|
|
257
|
-
it('should return media types specified in the constructor', function () {
|
|
258
|
-
const mediaTypes = ['text/plain', 'text/html'];
|
|
259
|
-
const S = new TrieRouterOptions({ignoredMediaTypes: mediaTypes});
|
|
260
|
-
const res = S.getIgnoredMediaTypes();
|
|
261
|
-
expect(res).to.be.eql(mediaTypes);
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it('should return media types added by the "addIgnoredMediaType" method', function () {
|
|
265
|
-
const mediaTypes = ['text/plain', 'text/html'];
|
|
266
|
-
const S = new TrieRouterOptions();
|
|
267
|
-
S.addIgnoredMediaType(mediaTypes[0]);
|
|
268
|
-
S.addIgnoredMediaType(mediaTypes[1]);
|
|
269
|
-
const res = S.getIgnoredMediaTypes();
|
|
270
|
-
expect(res).to.be.eql(mediaTypes);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it('should combine media types specified by the constructor and the "addIgnoredMediaType" method', function () {
|
|
105
|
+
it('should freeze the option "ignoredMediaTypes" to prevent mutations', function () {
|
|
274
106
|
const mediaTypes = ['text/plain', 'text/html'];
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
expect(res).to.be.eql(mediaTypes);
|
|
107
|
+
const inst = new TrieRouterOptions({ignoredMediaTypes: mediaTypes});
|
|
108
|
+
const res = inst.ignoredMediaTypes;
|
|
109
|
+
expect(Object.isFrozen(res)).to.be.true;
|
|
279
110
|
});
|
|
280
111
|
|
|
281
|
-
it('should
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
S.addIgnoredMediaType(mediaTypes[0]);
|
|
285
|
-
S.addIgnoredMediaType(mediaTypes[1]);
|
|
286
|
-
const res1 = S.getIgnoredMediaTypes();
|
|
287
|
-
const res2 = S.getIgnoredMediaTypes();
|
|
288
|
-
expect(res1).to.be.eql(mediaTypes);
|
|
289
|
-
expect(res2).to.be.eql(mediaTypes);
|
|
290
|
-
expect(res1).to.be.not.eq(res2);
|
|
291
|
-
res1[0] = '123';
|
|
292
|
-
const res3 = S.getIgnoredMediaTypes();
|
|
293
|
-
expect(res3).to.be.eql(mediaTypes);
|
|
112
|
+
it('should convert ignored media types to lower case', function () {
|
|
113
|
+
const inst = new TrieRouterOptions({ignoredMediaTypes: ['TEXT/PLAIN']});
|
|
114
|
+
expect(inst.ignoredMediaTypes).to.be.eql(['text/plain']);
|
|
294
115
|
});
|
|
295
116
|
});
|
|
296
117
|
});
|
package/src/trie-router.spec.js
CHANGED
|
@@ -28,7 +28,7 @@ describe('TrieRouter', function () {
|
|
|
28
28
|
const optionsInput = {requestBodyBytesLimit: 10};
|
|
29
29
|
const router = new TrieRouter(optionsInput);
|
|
30
30
|
const options = router.getService(TrieRouterOptions);
|
|
31
|
-
expect(options.
|
|
31
|
+
expect(options.requestBodyBytesLimit).to.be.eq(10);
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
it('should use the service container and the router options from parameters', function () {
|
|
@@ -37,7 +37,7 @@ describe('TrieRouter', function () {
|
|
|
37
37
|
const router = new TrieRouter(container, optionsInput);
|
|
38
38
|
const options = router.getService(TrieRouterOptions);
|
|
39
39
|
expect(router.container).to.be.eq(container);
|
|
40
|
-
expect(options.
|
|
40
|
+
expect(options.requestBodyBytesLimit).to.be.eq(10);
|
|
41
41
|
});
|
|
42
42
|
});
|
|
43
43
|
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import {Readable} from 'stream';
|
|
2
2
|
import {IncomingMessage} from 'http';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Request query input.
|
|
6
|
-
*/
|
|
7
|
-
type RequestQueryInput = {
|
|
8
|
-
[name: string]: unknown;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
4
|
/**
|
|
12
5
|
* Request headers input.
|
|
13
6
|
*/
|
|
@@ -24,7 +17,7 @@ type RequestOptions = {
|
|
|
24
17
|
secure?: boolean;
|
|
25
18
|
url?: string;
|
|
26
19
|
path?: string;
|
|
27
|
-
query?:
|
|
20
|
+
query?: string | object;
|
|
28
21
|
cookies?: object;
|
|
29
22
|
headers?: RequestHeadersInput;
|
|
30
23
|
body?: unknown;
|
|
@@ -116,12 +116,13 @@ export function createRequestMock(options) {
|
|
|
116
116
|
// options.query
|
|
117
117
|
if (options.query !== undefined) {
|
|
118
118
|
if (
|
|
119
|
-
|
|
120
|
-
typeof options.query !== '
|
|
119
|
+
options.query === null ||
|
|
120
|
+
(typeof options.query !== 'string' &&
|
|
121
|
+
typeof options.query !== 'object') ||
|
|
121
122
|
Array.isArray(options.query)
|
|
122
123
|
) {
|
|
123
124
|
throw new InvalidArgumentError(
|
|
124
|
-
'Option "query" must be an Object, but %v was given.',
|
|
125
|
+
'Option "query" must be a String or an Object, but %v was given.',
|
|
125
126
|
options.query,
|
|
126
127
|
);
|
|
127
128
|
}
|
|
@@ -335,7 +336,7 @@ function createRequestStream(secure, body, encoding) {
|
|
|
335
336
|
* Create request url.
|
|
336
337
|
*
|
|
337
338
|
* @param {string|undefined} path
|
|
338
|
-
* @param {object|undefined} query
|
|
339
|
+
* @param {string|object|undefined} query
|
|
339
340
|
* @returns {string}
|
|
340
341
|
*/
|
|
341
342
|
function createRequestUrl(path, query) {
|
|
@@ -346,9 +347,13 @@ function createRequestUrl(path, query) {
|
|
|
346
347
|
);
|
|
347
348
|
}
|
|
348
349
|
if (query !== undefined) {
|
|
349
|
-
if (
|
|
350
|
+
if (
|
|
351
|
+
query === null ||
|
|
352
|
+
(typeof query !== 'string' && typeof query !== 'object') ||
|
|
353
|
+
Array.isArray(query)
|
|
354
|
+
) {
|
|
350
355
|
throw new InvalidArgumentError(
|
|
351
|
-
'Parameter "query" must be an Object, but %v was given.',
|
|
356
|
+
'Parameter "query" must be a String or an Object, but %v was given.',
|
|
352
357
|
query,
|
|
353
358
|
);
|
|
354
359
|
}
|
|
@@ -359,6 +364,8 @@ function createRequestUrl(path, query) {
|
|
|
359
364
|
if (qs) {
|
|
360
365
|
res += `?${qs}`;
|
|
361
366
|
}
|
|
367
|
+
} else if (typeof query === 'string' && query !== '' && query !== '?') {
|
|
368
|
+
res += `?${query.replace(/^\?/, '')}`;
|
|
362
369
|
}
|
|
363
370
|
return res;
|
|
364
371
|
}
|
|
@@ -152,6 +152,26 @@ describe('createRequestMock', function () {
|
|
|
152
152
|
mustThrowWith('');
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
+
it('should require the option "query" to be a String or an Object', function () {
|
|
156
|
+
const throwable = v => () => createRequestMock({query: v});
|
|
157
|
+
const error = v =>
|
|
158
|
+
format(
|
|
159
|
+
'Option "query" must be a String or an Object, but %s was given.',
|
|
160
|
+
v,
|
|
161
|
+
);
|
|
162
|
+
expect(throwable(10)).to.throw(error('10'));
|
|
163
|
+
expect(throwable(0)).to.throw(error('0'));
|
|
164
|
+
expect(throwable(true)).to.throw(error('true'));
|
|
165
|
+
expect(throwable(false)).to.throw(error('false'));
|
|
166
|
+
expect(throwable([])).to.throw(error('Array'));
|
|
167
|
+
expect(throwable(null)).to.throw(error('null'));
|
|
168
|
+
throwable({foo: 'bar'})();
|
|
169
|
+
throwable({})();
|
|
170
|
+
throwable('foo=bar')();
|
|
171
|
+
throwable('')();
|
|
172
|
+
throwable(undefined)();
|
|
173
|
+
});
|
|
174
|
+
|
|
155
175
|
it('should require the option "cookies" to be an Object', function () {
|
|
156
176
|
const throwable = v => () => createRequestMock({cookies: v});
|
|
157
177
|
const error = v =>
|
|
@@ -336,17 +356,17 @@ describe('createRequestMock', function () {
|
|
|
336
356
|
expect(req.socket).to.be.instanceof(Socket);
|
|
337
357
|
});
|
|
338
358
|
|
|
339
|
-
it('should use the default
|
|
359
|
+
it('should use "/" as the default value of the request url', function () {
|
|
340
360
|
const req = createRequestMock();
|
|
341
361
|
expect(req.url).to.be.eq('/');
|
|
342
362
|
});
|
|
343
363
|
|
|
344
|
-
it('should use "localhost" as the default "host" header', function () {
|
|
364
|
+
it('should use "localhost" as the default value of the "host" header', function () {
|
|
345
365
|
const req = createRequestMock();
|
|
346
366
|
expect(req.headers).to.be.eql({host: 'localhost'});
|
|
347
367
|
});
|
|
348
368
|
|
|
349
|
-
it('should use
|
|
369
|
+
it('should use "utf-8" as the default value of the data encoding', async function () {
|
|
350
370
|
const body = 'test';
|
|
351
371
|
const req = createRequestMock({body: Buffer.from(body)});
|
|
352
372
|
const chunks = [];
|
|
@@ -416,7 +436,43 @@ describe('createRequestMock', function () {
|
|
|
416
436
|
expect(data).to.be.eq(body);
|
|
417
437
|
});
|
|
418
438
|
|
|
419
|
-
it('should pass
|
|
439
|
+
it('should pass a number from the "body" option to the stream as a string', async function () {
|
|
440
|
+
const body = 10;
|
|
441
|
+
const req = createRequestMock({body});
|
|
442
|
+
const chunks = [];
|
|
443
|
+
const data = await new Promise((resolve, reject) => {
|
|
444
|
+
req.on('data', chunk => chunks.push(Buffer.from(chunk)));
|
|
445
|
+
req.on('error', err => reject(err));
|
|
446
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
447
|
+
});
|
|
448
|
+
expect(data).to.be.eq('10');
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('should pass a boolean from the "body" option to the stream as a string', async function () {
|
|
452
|
+
const body = true;
|
|
453
|
+
const req = createRequestMock({body});
|
|
454
|
+
const chunks = [];
|
|
455
|
+
const data = await new Promise((resolve, reject) => {
|
|
456
|
+
req.on('data', chunk => chunks.push(Buffer.from(chunk)));
|
|
457
|
+
req.on('error', err => reject(err));
|
|
458
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
459
|
+
});
|
|
460
|
+
expect(data).to.be.eq('true');
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it('should pass an array from the "body" option to the stream as JSON', async function () {
|
|
464
|
+
const body = [1, 2];
|
|
465
|
+
const req = createRequestMock({body});
|
|
466
|
+
const chunks = [];
|
|
467
|
+
const data = await new Promise((resolve, reject) => {
|
|
468
|
+
req.on('data', chunk => chunks.push(Buffer.from(chunk)));
|
|
469
|
+
req.on('error', err => reject(err));
|
|
470
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
471
|
+
});
|
|
472
|
+
expect(data).to.be.eq(JSON.stringify(body));
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('should pass an object from the "body" option to the stream as JSON', async function () {
|
|
420
476
|
const body = {foo: 'bar'};
|
|
421
477
|
const req = createRequestMock({body});
|
|
422
478
|
const chunks = [];
|
|
@@ -428,7 +484,7 @@ describe('createRequestMock', function () {
|
|
|
428
484
|
expect(data).to.be.eq(JSON.stringify(body));
|
|
429
485
|
});
|
|
430
486
|
|
|
431
|
-
it('should pass a Buffer body to the stream', async function () {
|
|
487
|
+
it('should pass a Buffer from the "body" option to the stream', async function () {
|
|
432
488
|
const body = Buffer.from('test');
|
|
433
489
|
const req = createRequestMock({body});
|
|
434
490
|
const chunks = [];
|
|
@@ -440,27 +496,38 @@ describe('createRequestMock', function () {
|
|
|
440
496
|
expect(data).to.be.eql(body);
|
|
441
497
|
});
|
|
442
498
|
|
|
443
|
-
it('should
|
|
499
|
+
it('should pass a value form the "url" option to the request url', function () {
|
|
444
500
|
const req = createRequestMock({url: '/test'});
|
|
445
501
|
expect(req.url).to.be.eq('/test');
|
|
446
502
|
});
|
|
447
503
|
|
|
448
|
-
it('should
|
|
504
|
+
it('should pass a value form the "path" option to the request url', function () {
|
|
449
505
|
const req = createRequestMock({path: '/test'});
|
|
450
506
|
expect(req.url).to.be.eq('/test');
|
|
451
507
|
});
|
|
452
508
|
|
|
453
|
-
it('should
|
|
509
|
+
it('should pass a string form the "query" option to the request url', async function () {
|
|
510
|
+
const req1 = createRequestMock({query: 'p1=foo&p2=bar'});
|
|
511
|
+
const req2 = createRequestMock({query: '?p1=foo&p2=bar'});
|
|
512
|
+
expect(req1.url).to.be.eq('/?p1=foo&p2=bar');
|
|
513
|
+
expect(req2.url).to.be.eq('/?p1=foo&p2=bar');
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it('should pass an object form the "query" option to the request url', async function () {
|
|
454
517
|
const req = createRequestMock({query: {foo: 'bar', baz: 'qux'}});
|
|
455
518
|
expect(req.url).to.be.eq('/?foo=bar&baz=qux');
|
|
456
519
|
});
|
|
457
520
|
|
|
458
521
|
it('should combine the "path" and "query" options in the request url', function () {
|
|
459
|
-
const
|
|
460
|
-
|
|
522
|
+
const req1 = createRequestMock({path: '/test', query: 'foo=bar'});
|
|
523
|
+
const req2 = createRequestMock({path: '/test', query: '?foo=bar'});
|
|
524
|
+
const req3 = createRequestMock({path: '/test', query: {foo: 'bar'}});
|
|
525
|
+
expect(req1.url).to.be.eq('/test?foo=bar');
|
|
526
|
+
expect(req2.url).to.be.eq('/test?foo=bar');
|
|
527
|
+
expect(req3.url).to.be.eq('/test?foo=bar');
|
|
461
528
|
});
|
|
462
529
|
|
|
463
|
-
it('should set the "method" option to the request method in upper case', async function () {
|
|
530
|
+
it('should set a value from the "method" option to the request method in upper case', async function () {
|
|
464
531
|
const req1 = createRequestMock({method: 'get'});
|
|
465
532
|
const req2 = createRequestMock({method: 'post'});
|
|
466
533
|
expect(req1.method).to.be.eq('GET');
|
|
@@ -478,12 +545,12 @@ describe('createRequestMock', function () {
|
|
|
478
545
|
expect(req.headers['x-forwarded-proto']).to.be.eq('https');
|
|
479
546
|
});
|
|
480
547
|
|
|
481
|
-
it('should set the "
|
|
548
|
+
it('should serialize and set a value from the "cookies" option to the "cookie" header', function () {
|
|
482
549
|
const req = createRequestMock({cookies: {p1: 'foo', p2: 'bar'}});
|
|
483
550
|
expect(req.headers['cookie']).to.be.eq('p1=foo; p2=bar');
|
|
484
551
|
});
|
|
485
552
|
|
|
486
|
-
it('should merge the "cookie" header with the "
|
|
553
|
+
it('should merge the "cookie" header with the "cookies" option', function () {
|
|
487
554
|
const req = createRequestMock({
|
|
488
555
|
headers: {cookie: 'p1=foo; p2=bar'},
|
|
489
556
|
cookies: {p2: 'baz', p3: 'qux'},
|