@boo-labs/server 1.0.1 → 1.0.2
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/package.json +1 -1
- package/source/core/IPCMain/IPCProtocol.mjs +80 -75
- package/source/shared/domain/UUIDV7/index.mjs +31 -39
- package/source/shared/errors/ServerError/index.mjs +48 -2
- package/source/shared/outcome/Result/index.mjs +77 -130
- package/source/shared/outcome/Validation/index.mjs +56 -114
- package/source/shared/validations/Middleware/index.mjs +32 -23
- package/source/shared/validations/PathnamePattern/index.mjs +38 -39
- package/source/shared/validations/Server/NotFoundHandler/index.mjs +25 -15
- package/source/shared/validations/Server/Router/index.mjs +24 -14
- package/source/shared/validations/Server/ServerConfiguration/index.mjs +24 -14
- package/source/shared/validations/Server/SystemErrorHandler/index.mjs +25 -15
- package/source/shared/validations/ServerConfiguration/CORSOptions/index.mjs +95 -91
- package/source/shared/validations/ServerConfiguration/CSRFOptions/index.mjs +96 -96
- package/source/shared/validations/ServerConfiguration/CompressOptions/index.mjs +55 -43
- package/source/shared/validations/ServerConfiguration/CookieParserOptions/index.mjs +28 -13
- package/source/shared/validations/ServerConfiguration/CookieSessionOptions/index.mjs +101 -111
- package/source/shared/validations/ServerConfiguration/ListenOptions/index.mjs +55 -43
- package/source/shared/validations/ServerConfiguration/RateLimitOptions/index.mjs +71 -61
- package/source/shared/validations/ServerConfiguration/RequestOptions/index.mjs +44 -31
- package/source/shared/validations/ServerConfiguration/ResponseOptions/index.mjs +39 -25
- package/source/shared/validations/ServerConfiguration/SecurityHeadersOptions/index.mjs +27 -14
- package/source/shared/validations/ServerConfiguration/StaticOptions/index.mjs +85 -84
- package/source/shared/outcome/ErrorSchema.mjs +0 -361
|
@@ -13,6 +13,20 @@
|
|
|
13
13
|
import Validation from '#shared/outcome/Validation';
|
|
14
14
|
import ServerConfigurationError from '#shared/errors/ServerConfigurationError';
|
|
15
15
|
|
|
16
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* A successful {@link Validation} instance.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {import('#shared/outcome/Validation').SuccessValidation} SuccessValidation
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A failed {@link Validation} instance.
|
|
25
|
+
*
|
|
26
|
+
* @template {Error} E
|
|
27
|
+
* @typedef {import('#shared/outcome/Validation').FailureValidation<E>} FailureValidation
|
|
28
|
+
*/
|
|
29
|
+
|
|
16
30
|
// ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
31
|
/**
|
|
18
32
|
* Minimum valid TCP port.
|
|
@@ -42,6 +56,7 @@ function isPlainObject(value) {
|
|
|
42
56
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
43
57
|
}
|
|
44
58
|
|
|
59
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
45
60
|
/**
|
|
46
61
|
* Validates listen options.
|
|
47
62
|
*
|
|
@@ -54,7 +69,8 @@ function isPlainObject(value) {
|
|
|
54
69
|
* 5. `backlog`, if present, must be a number.
|
|
55
70
|
*
|
|
56
71
|
* @param {unknown} options - Listen options to validate.
|
|
57
|
-
* @returns {
|
|
72
|
+
* @returns {SuccessValidation|FailureValidation<ServerConfigurationError>} Validation result.
|
|
73
|
+
*
|
|
58
74
|
* @example
|
|
59
75
|
* ```js
|
|
60
76
|
* const validation = validateListenOptions({ port: 3000 });
|
|
@@ -77,33 +93,31 @@ export function validateListenOptions(options) {
|
|
|
77
93
|
}
|
|
78
94
|
|
|
79
95
|
if (!isPlainObject(options)) {
|
|
80
|
-
return Validation.failure(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
});
|
|
96
|
+
return Validation.failure(
|
|
97
|
+
new ServerConfigurationError('INVALID_LISTEN_OPTIONS', {
|
|
98
|
+
values: { received: typeof options },
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
86
101
|
}
|
|
87
102
|
|
|
88
103
|
if ('port' in options) {
|
|
89
104
|
const { port } = options;
|
|
90
105
|
|
|
91
106
|
if (typeof port !== 'number') {
|
|
92
|
-
return Validation.failure(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
});
|
|
107
|
+
return Validation.failure(
|
|
108
|
+
new ServerConfigurationError('INVALID_PORT_TYPE', {
|
|
109
|
+
values: { received: typeof port },
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
if (!Number.isInteger(port) || port < MIN_PORT || port > MAX_PORT) {
|
|
101
|
-
return Validation.failure(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
115
|
+
return Validation.failure(
|
|
116
|
+
new ServerConfigurationError('INVALID_PORT_RANGE', {
|
|
117
|
+
values: { port },
|
|
118
|
+
details: { port },
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
107
121
|
}
|
|
108
122
|
}
|
|
109
123
|
|
|
@@ -111,21 +125,19 @@ export function validateListenOptions(options) {
|
|
|
111
125
|
const { host } = options;
|
|
112
126
|
|
|
113
127
|
if (typeof host !== 'string') {
|
|
114
|
-
return Validation.failure(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
});
|
|
128
|
+
return Validation.failure(
|
|
129
|
+
new ServerConfigurationError('INVALID_HOST_TYPE', {
|
|
130
|
+
values: { received: typeof host },
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
120
133
|
}
|
|
121
134
|
|
|
122
135
|
if (host.length === 0) {
|
|
123
|
-
return Validation.failure(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
});
|
|
136
|
+
return Validation.failure(
|
|
137
|
+
new ServerConfigurationError('INVALID_HOST_VALUE', {
|
|
138
|
+
details: { host },
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
129
141
|
}
|
|
130
142
|
}
|
|
131
143
|
|
|
@@ -133,21 +145,20 @@ export function validateListenOptions(options) {
|
|
|
133
145
|
const { backlog } = options;
|
|
134
146
|
|
|
135
147
|
if (typeof backlog !== 'number') {
|
|
136
|
-
return Validation.failure(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
});
|
|
148
|
+
return Validation.failure(
|
|
149
|
+
new ServerConfigurationError('INVALID_BACKLOG_TYPE', {
|
|
150
|
+
values: { received: typeof backlog },
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
142
153
|
}
|
|
143
154
|
|
|
144
155
|
if (!Number.isInteger(backlog) || backlog <= 0) {
|
|
145
|
-
return Validation.failure(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
156
|
+
return Validation.failure(
|
|
157
|
+
new ServerConfigurationError('INVALID_BACKLOG_RANGE', {
|
|
158
|
+
values: { backlog },
|
|
159
|
+
details: { backlog },
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
151
162
|
}
|
|
152
163
|
}
|
|
153
164
|
|
|
@@ -164,6 +175,7 @@ export function validateListenOptions(options) {
|
|
|
164
175
|
* @throws {ServerConfigurationError} If `host` is not a string.
|
|
165
176
|
* @throws {ServerConfigurationError} If `host` is an empty string.
|
|
166
177
|
* @throws {ServerConfigurationError} If `backlog` is not a number.
|
|
178
|
+
*
|
|
167
179
|
* @example
|
|
168
180
|
* ```js
|
|
169
181
|
* assertListenOptions({ port: 3000, host: '127.0.0.1' });
|
|
@@ -14,6 +14,20 @@
|
|
|
14
14
|
import Validation from '#shared/outcome/Validation';
|
|
15
15
|
import ServerConfigurationError from '#shared/errors/ServerConfigurationError';
|
|
16
16
|
|
|
17
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
18
|
+
/**
|
|
19
|
+
* A successful {@link Validation} instance.
|
|
20
|
+
*
|
|
21
|
+
* @typedef {import('#shared/outcome/Validation').SuccessValidation} SuccessValidation
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A failed {@link Validation} instance.
|
|
26
|
+
*
|
|
27
|
+
* @template {Error} E
|
|
28
|
+
* @typedef {import('#shared/outcome/Validation').FailureValidation<E>} FailureValidation
|
|
29
|
+
*/
|
|
30
|
+
|
|
17
31
|
// ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
18
32
|
/**
|
|
19
33
|
* Minimum valid HTTP status code for a rate-limit rejection.
|
|
@@ -43,6 +57,7 @@ function isPlainObject(value) {
|
|
|
43
57
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
44
58
|
}
|
|
45
59
|
|
|
60
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
46
61
|
/**
|
|
47
62
|
* Validates rate limit options.
|
|
48
63
|
*
|
|
@@ -61,7 +76,8 @@ function isPlainObject(value) {
|
|
|
61
76
|
* 11. `legacyHeaders`, if present, must be a boolean.
|
|
62
77
|
*
|
|
63
78
|
* @param {unknown} options - Rate limit options to validate.
|
|
64
|
-
* @returns {
|
|
79
|
+
* @returns {SuccessValidation|FailureValidation<ServerConfigurationError>} Validation result.
|
|
80
|
+
*
|
|
65
81
|
* @example
|
|
66
82
|
* ```js
|
|
67
83
|
* const validation = validateRateLimitOptions({ windowMs: 60_000, max: 100 });
|
|
@@ -84,33 +100,31 @@ export function validateRateLimitOptions(options) {
|
|
|
84
100
|
}
|
|
85
101
|
|
|
86
102
|
if (!isPlainObject(options)) {
|
|
87
|
-
return Validation.failure(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
});
|
|
103
|
+
return Validation.failure(
|
|
104
|
+
new ServerConfigurationError('INVALID_RATE_LIMIT_OPTIONS', {
|
|
105
|
+
values: { received: typeof options },
|
|
106
|
+
}),
|
|
107
|
+
);
|
|
93
108
|
}
|
|
94
109
|
|
|
95
110
|
if ('windowMs' in options) {
|
|
96
111
|
const { windowMs } = options;
|
|
97
112
|
|
|
98
113
|
if (typeof windowMs !== 'number') {
|
|
99
|
-
return Validation.failure(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
});
|
|
114
|
+
return Validation.failure(
|
|
115
|
+
new ServerConfigurationError('INVALID_RATE_LIMIT_WINDOW_MS_TYPE', {
|
|
116
|
+
values: { received: typeof windowMs },
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
105
119
|
}
|
|
106
120
|
|
|
107
121
|
if (!Number.isInteger(windowMs) || windowMs <= 0) {
|
|
108
|
-
return Validation.failure(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
122
|
+
return Validation.failure(
|
|
123
|
+
new ServerConfigurationError('INVALID_RATE_LIMIT_WINDOW_MS_RANGE', {
|
|
124
|
+
values: { windowMs },
|
|
125
|
+
details: { windowMs },
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
114
128
|
}
|
|
115
129
|
}
|
|
116
130
|
|
|
@@ -118,21 +132,20 @@ export function validateRateLimitOptions(options) {
|
|
|
118
132
|
const { max } = options;
|
|
119
133
|
|
|
120
134
|
if (typeof max !== 'number') {
|
|
121
|
-
return Validation.failure(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
});
|
|
135
|
+
return Validation.failure(
|
|
136
|
+
new ServerConfigurationError('INVALID_RATE_LIMIT_MAX_TYPE', {
|
|
137
|
+
values: { received: typeof max },
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
127
140
|
}
|
|
128
141
|
|
|
129
142
|
if (!Number.isInteger(max) || max <= 0) {
|
|
130
|
-
return Validation.failure(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
143
|
+
return Validation.failure(
|
|
144
|
+
new ServerConfigurationError('INVALID_RATE_LIMIT_MAX_RANGE', {
|
|
145
|
+
values: { max },
|
|
146
|
+
details: { max },
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
136
149
|
}
|
|
137
150
|
}
|
|
138
151
|
|
|
@@ -140,12 +153,11 @@ export function validateRateLimitOptions(options) {
|
|
|
140
153
|
const { statusCode } = options;
|
|
141
154
|
|
|
142
155
|
if (typeof statusCode !== 'number') {
|
|
143
|
-
return Validation.failure(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
});
|
|
156
|
+
return Validation.failure(
|
|
157
|
+
new ServerConfigurationError('INVALID_RATE_LIMIT_STATUS_CODE_TYPE', {
|
|
158
|
+
values: { received: typeof statusCode },
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
149
161
|
}
|
|
150
162
|
|
|
151
163
|
if (
|
|
@@ -153,12 +165,12 @@ export function validateRateLimitOptions(options) {
|
|
|
153
165
|
statusCode < MIN_STATUS_CODE ||
|
|
154
166
|
statusCode > MAX_STATUS_CODE
|
|
155
167
|
) {
|
|
156
|
-
return Validation.failure(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
168
|
+
return Validation.failure(
|
|
169
|
+
new ServerConfigurationError('INVALID_RATE_LIMIT_STATUS_CODE_RANGE', {
|
|
170
|
+
values: { statusCode },
|
|
171
|
+
details: { statusCode },
|
|
172
|
+
}),
|
|
173
|
+
);
|
|
162
174
|
}
|
|
163
175
|
}
|
|
164
176
|
|
|
@@ -166,12 +178,11 @@ export function validateRateLimitOptions(options) {
|
|
|
166
178
|
const { message } = options;
|
|
167
179
|
|
|
168
180
|
if (!isPlainObject(message)) {
|
|
169
|
-
return Validation.failure(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
});
|
|
181
|
+
return Validation.failure(
|
|
182
|
+
new ServerConfigurationError('INVALID_RATE_LIMIT_MESSAGE_TYPE', {
|
|
183
|
+
values: { received: typeof message },
|
|
184
|
+
}),
|
|
185
|
+
);
|
|
175
186
|
}
|
|
176
187
|
}
|
|
177
188
|
|
|
@@ -179,12 +190,11 @@ export function validateRateLimitOptions(options) {
|
|
|
179
190
|
const { standardHeaders } = options;
|
|
180
191
|
|
|
181
192
|
if (typeof standardHeaders !== 'boolean') {
|
|
182
|
-
return Validation.failure(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
});
|
|
193
|
+
return Validation.failure(
|
|
194
|
+
new ServerConfigurationError('INVALID_RATE_LIMIT_STANDARD_HEADERS_TYPE', {
|
|
195
|
+
values: { received: typeof standardHeaders },
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
188
198
|
}
|
|
189
199
|
}
|
|
190
200
|
|
|
@@ -192,12 +202,11 @@ export function validateRateLimitOptions(options) {
|
|
|
192
202
|
const { legacyHeaders } = options;
|
|
193
203
|
|
|
194
204
|
if (typeof legacyHeaders !== 'boolean') {
|
|
195
|
-
return Validation.failure(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
});
|
|
205
|
+
return Validation.failure(
|
|
206
|
+
new ServerConfigurationError('INVALID_RATE_LIMIT_LEGACY_HEADERS_TYPE', {
|
|
207
|
+
values: { received: typeof legacyHeaders },
|
|
208
|
+
}),
|
|
209
|
+
);
|
|
201
210
|
}
|
|
202
211
|
}
|
|
203
212
|
|
|
@@ -215,6 +224,7 @@ export function validateRateLimitOptions(options) {
|
|
|
215
224
|
* @throws {ServerConfigurationError} If `message` is not a plain object.
|
|
216
225
|
* @throws {ServerConfigurationError} If `standardHeaders` is not a boolean.
|
|
217
226
|
* @throws {ServerConfigurationError} If `legacyHeaders` is not a boolean.
|
|
227
|
+
*
|
|
218
228
|
* @example
|
|
219
229
|
* ```js
|
|
220
230
|
* assertRateLimitOptions({ windowMs: 60_000, max: 100 });
|
|
@@ -13,6 +13,20 @@
|
|
|
13
13
|
import Validation from '#shared/outcome/Validation';
|
|
14
14
|
import ServerConfigurationError from '#shared/errors/ServerConfigurationError';
|
|
15
15
|
|
|
16
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* A successful {@link Validation} instance.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {import('#shared/outcome/Validation').SuccessValidation} SuccessValidation
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A failed {@link Validation} instance.
|
|
25
|
+
*
|
|
26
|
+
* @template {Error} E
|
|
27
|
+
* @typedef {import('#shared/outcome/Validation').FailureValidation<E>} FailureValidation
|
|
28
|
+
*/
|
|
29
|
+
|
|
16
30
|
// ━━ FUNCTIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
31
|
/**
|
|
18
32
|
* Determines whether a value is a non-null plain object.
|
|
@@ -25,6 +39,7 @@ function isPlainObject(value) {
|
|
|
25
39
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
26
40
|
}
|
|
27
41
|
|
|
42
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
28
43
|
/**
|
|
29
44
|
* Validates request options.
|
|
30
45
|
*
|
|
@@ -36,7 +51,7 @@ function isPlainObject(value) {
|
|
|
36
51
|
* 4. `trustProxy`, if defined, must be a boolean or a non-negative integer.
|
|
37
52
|
*
|
|
38
53
|
* @param {unknown} options - Request options to validate.
|
|
39
|
-
* @returns {
|
|
54
|
+
* @returns {SuccessValidation|FailureValidation<ServerConfigurationError>} Validation result.
|
|
40
55
|
*/
|
|
41
56
|
export function validateRequestOptions(options) {
|
|
42
57
|
if (options === undefined) {
|
|
@@ -44,12 +59,11 @@ export function validateRequestOptions(options) {
|
|
|
44
59
|
}
|
|
45
60
|
|
|
46
61
|
if (!isPlainObject(options)) {
|
|
47
|
-
return Validation.failure(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
});
|
|
62
|
+
return Validation.failure(
|
|
63
|
+
new ServerConfigurationError('INVALID_REQUEST_OPTIONS', {
|
|
64
|
+
values: { received: typeof options },
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
53
67
|
}
|
|
54
68
|
|
|
55
69
|
if ('bodyLimit' in options) {
|
|
@@ -57,21 +71,20 @@ export function validateRequestOptions(options) {
|
|
|
57
71
|
|
|
58
72
|
if (bodyLimit !== undefined) {
|
|
59
73
|
if (typeof bodyLimit !== 'number') {
|
|
60
|
-
return Validation.failure(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
});
|
|
74
|
+
return Validation.failure(
|
|
75
|
+
new ServerConfigurationError('INVALID_BODY_LIMIT_TYPE', {
|
|
76
|
+
values: { received: typeof bodyLimit },
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
66
79
|
}
|
|
67
80
|
|
|
68
81
|
if (!Number.isSafeInteger(bodyLimit) || bodyLimit <= 0) {
|
|
69
|
-
return Validation.failure(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
return Validation.failure(
|
|
83
|
+
new ServerConfigurationError('INVALID_BODY_LIMIT_RANGE', {
|
|
84
|
+
values: { bodyLimit },
|
|
85
|
+
details: { bodyLimit },
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
75
88
|
}
|
|
76
89
|
}
|
|
77
90
|
}
|
|
@@ -84,21 +97,20 @@ export function validateRequestOptions(options) {
|
|
|
84
97
|
const isNumber = typeof trustProxy === 'number';
|
|
85
98
|
|
|
86
99
|
if (!isBoolean && !isNumber) {
|
|
87
|
-
return Validation.failure(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
});
|
|
100
|
+
return Validation.failure(
|
|
101
|
+
new ServerConfigurationError('INVALID_TRUST_PROXY_TYPE', {
|
|
102
|
+
values: { received: typeof trustProxy },
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
93
105
|
}
|
|
94
106
|
|
|
95
107
|
if (isNumber && (!Number.isInteger(trustProxy) || trustProxy < 0)) {
|
|
96
|
-
return Validation.failure(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
return Validation.failure(
|
|
109
|
+
new ServerConfigurationError('INVALID_TRUST_PROXY_RANGE', {
|
|
110
|
+
values: { trustProxy },
|
|
111
|
+
details: { trustProxy },
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
102
114
|
}
|
|
103
115
|
}
|
|
104
116
|
}
|
|
@@ -115,6 +127,7 @@ export function validateRequestOptions(options) {
|
|
|
115
127
|
* @throws {ServerConfigurationError} If `bodyLimit` is defined and is not a positive safe integer.
|
|
116
128
|
* @throws {ServerConfigurationError} If `trustProxy` is defined and is not a boolean or number.
|
|
117
129
|
* @throws {ServerConfigurationError} If numeric `trustProxy` is defined and is negative or not an integer.
|
|
130
|
+
*
|
|
118
131
|
* @example
|
|
119
132
|
* ```js
|
|
120
133
|
* assertRequestOptions({
|
|
@@ -13,6 +13,20 @@
|
|
|
13
13
|
import Validation from '#shared/outcome/Validation';
|
|
14
14
|
import ServerConfigurationError from '#shared/errors/ServerConfigurationError';
|
|
15
15
|
|
|
16
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* A successful {@link Validation} instance.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {import('#shared/outcome/Validation').SuccessValidation} SuccessValidation
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A failed {@link Validation} instance.
|
|
25
|
+
*
|
|
26
|
+
* @template {Error} E
|
|
27
|
+
* @typedef {import('#shared/outcome/Validation').FailureValidation<E>} FailureValidation
|
|
28
|
+
*/
|
|
29
|
+
|
|
16
30
|
// ━━ FUNCTIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
31
|
/**
|
|
18
32
|
* Determines whether a value is a non-null plain object.
|
|
@@ -25,6 +39,7 @@ function isPlainObject(value) {
|
|
|
25
39
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
26
40
|
}
|
|
27
41
|
|
|
42
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
28
43
|
/**
|
|
29
44
|
* Validates response options.
|
|
30
45
|
*
|
|
@@ -36,7 +51,8 @@ function isPlainObject(value) {
|
|
|
36
51
|
* 4. `defaultCharset`, if defined, must be a non-empty string.
|
|
37
52
|
*
|
|
38
53
|
* @param {unknown} options - Response options to validate.
|
|
39
|
-
* @returns {
|
|
54
|
+
* @returns {SuccessValidation|FailureValidation<ServerConfigurationError>} Validation result.
|
|
55
|
+
*
|
|
40
56
|
* @example
|
|
41
57
|
* ```js
|
|
42
58
|
* const validation = validateResponseOptions({ etag: true });
|
|
@@ -51,12 +67,11 @@ export function validateResponseOptions(options) {
|
|
|
51
67
|
}
|
|
52
68
|
|
|
53
69
|
if (!isPlainObject(options)) {
|
|
54
|
-
return Validation.failure(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
});
|
|
70
|
+
return Validation.failure(
|
|
71
|
+
new ServerConfigurationError('INVALID_RESPONSE_OPTIONS', {
|
|
72
|
+
values: { received: typeof options },
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
60
75
|
}
|
|
61
76
|
|
|
62
77
|
if ('etag' in options) {
|
|
@@ -64,12 +79,11 @@ export function validateResponseOptions(options) {
|
|
|
64
79
|
|
|
65
80
|
if (etag !== undefined) {
|
|
66
81
|
if (typeof etag !== 'boolean') {
|
|
67
|
-
return Validation.failure(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
});
|
|
82
|
+
return Validation.failure(
|
|
83
|
+
new ServerConfigurationError('INVALID_ETAG_TYPE', {
|
|
84
|
+
values: { received: typeof etag },
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
73
87
|
}
|
|
74
88
|
}
|
|
75
89
|
}
|
|
@@ -79,21 +93,19 @@ export function validateResponseOptions(options) {
|
|
|
79
93
|
|
|
80
94
|
if (defaultCharset !== undefined) {
|
|
81
95
|
if (typeof defaultCharset !== 'string') {
|
|
82
|
-
return Validation.failure(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
96
|
+
return Validation.failure(
|
|
97
|
+
new ServerConfigurationError('INVALID_DEFAULT_CHARSET_TYPE', {
|
|
98
|
+
values: { received: typeof defaultCharset },
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
88
101
|
}
|
|
89
102
|
|
|
90
103
|
if (defaultCharset.trim().length === 0) {
|
|
91
|
-
return Validation.failure(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
});
|
|
104
|
+
return Validation.failure(
|
|
105
|
+
new ServerConfigurationError('INVALID_DEFAULT_CHARSET_VALUE', {
|
|
106
|
+
details: { defaultCharset },
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
97
109
|
}
|
|
98
110
|
}
|
|
99
111
|
}
|
|
@@ -109,6 +121,7 @@ export function validateResponseOptions(options) {
|
|
|
109
121
|
* @throws {ServerConfigurationError} If `etag` is defined and is not a boolean.
|
|
110
122
|
* @throws {ServerConfigurationError} If `defaultCharset` is defined and is not a string.
|
|
111
123
|
* @throws {ServerConfigurationError} If `defaultCharset` is defined and is an empty string.
|
|
124
|
+
*
|
|
112
125
|
* @example
|
|
113
126
|
* ```js
|
|
114
127
|
* assertResponseOptions({ etag: true, defaultCharset: 'utf-8' });
|
|
@@ -116,6 +129,7 @@ export function validateResponseOptions(options) {
|
|
|
116
129
|
*/
|
|
117
130
|
export function assertResponseOptions(options) {
|
|
118
131
|
const validation = validateResponseOptions(options);
|
|
132
|
+
|
|
119
133
|
if (!validation.ok) {
|
|
120
134
|
throw validation.error;
|
|
121
135
|
}
|