@boo-labs/server 1.0.0 → 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.
Files changed (26) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +125 -67
  3. package/package.json +1 -1
  4. package/source/core/IPCMain/IPCProtocol.mjs +80 -75
  5. package/source/shared/domain/UUIDV7/index.mjs +31 -39
  6. package/source/shared/errors/ServerError/index.mjs +48 -2
  7. package/source/shared/outcome/Result/index.mjs +77 -130
  8. package/source/shared/outcome/Validation/index.mjs +56 -114
  9. package/source/shared/validations/Middleware/index.mjs +32 -23
  10. package/source/shared/validations/PathnamePattern/index.mjs +38 -39
  11. package/source/shared/validations/Server/NotFoundHandler/index.mjs +25 -15
  12. package/source/shared/validations/Server/Router/index.mjs +24 -14
  13. package/source/shared/validations/Server/ServerConfiguration/index.mjs +24 -14
  14. package/source/shared/validations/Server/SystemErrorHandler/index.mjs +25 -15
  15. package/source/shared/validations/ServerConfiguration/CORSOptions/index.mjs +95 -91
  16. package/source/shared/validations/ServerConfiguration/CSRFOptions/index.mjs +96 -96
  17. package/source/shared/validations/ServerConfiguration/CompressOptions/index.mjs +55 -43
  18. package/source/shared/validations/ServerConfiguration/CookieParserOptions/index.mjs +28 -13
  19. package/source/shared/validations/ServerConfiguration/CookieSessionOptions/index.mjs +101 -111
  20. package/source/shared/validations/ServerConfiguration/ListenOptions/index.mjs +55 -43
  21. package/source/shared/validations/ServerConfiguration/RateLimitOptions/index.mjs +71 -61
  22. package/source/shared/validations/ServerConfiguration/RequestOptions/index.mjs +44 -31
  23. package/source/shared/validations/ServerConfiguration/ResponseOptions/index.mjs +39 -25
  24. package/source/shared/validations/ServerConfiguration/SecurityHeadersOptions/index.mjs +27 -14
  25. package/source/shared/validations/ServerConfiguration/StaticOptions/index.mjs +85 -84
  26. 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 {Validation} Validation result.
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
- error: ServerConfigurationError,
82
- code: 'INVALID_LISTEN_OPTIONS',
83
- custom: true,
84
- options: { values: { received: typeof options } },
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
- error: ServerConfigurationError,
94
- code: 'INVALID_PORT_TYPE',
95
- custom: true,
96
- options: { values: { received: typeof port } },
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
- error: ServerConfigurationError,
103
- code: 'INVALID_PORT_RANGE',
104
- custom: true,
105
- options: { details: { port } },
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
- error: ServerConfigurationError,
116
- code: 'INVALID_HOST_TYPE',
117
- custom: true,
118
- options: { values: { received: typeof host } },
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
- error: ServerConfigurationError,
125
- code: 'INVALID_HOST_VALUE',
126
- custom: true,
127
- options: { details: { host } },
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
- error: ServerConfigurationError,
138
- code: 'INVALID_BACKLOG_TYPE',
139
- custom: true,
140
- options: { values: { received: typeof backlog } },
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
- error: ServerConfigurationError,
147
- code: 'INVALID_BACKLOG_RANGE',
148
- custom: true,
149
- options: { details: { backlog } },
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 {Validation} Validation result.
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
- error: ServerConfigurationError,
89
- code: 'INVALID_RATE_LIMIT_OPTIONS',
90
- custom: true,
91
- options: { values: { received: typeof options } },
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
- error: ServerConfigurationError,
101
- code: 'INVALID_RATE_LIMIT_WINDOW_MS_TYPE',
102
- custom: true,
103
- options: { values: { received: typeof windowMs } },
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
- error: ServerConfigurationError,
110
- code: 'INVALID_RATE_LIMIT_WINDOW_MS_RANGE',
111
- custom: true,
112
- options: { details: { windowMs } },
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
- error: ServerConfigurationError,
123
- code: 'INVALID_RATE_LIMIT_MAX_TYPE',
124
- custom: true,
125
- options: { values: { received: typeof max } },
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
- error: ServerConfigurationError,
132
- code: 'INVALID_RATE_LIMIT_MAX_RANGE',
133
- custom: true,
134
- options: { details: { max } },
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
- error: ServerConfigurationError,
145
- code: 'INVALID_RATE_LIMIT_STATUS_CODE_TYPE',
146
- custom: true,
147
- options: { values: { received: typeof statusCode } },
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
- error: ServerConfigurationError,
158
- code: 'INVALID_RATE_LIMIT_STATUS_CODE_RANGE',
159
- custom: true,
160
- options: { details: { statusCode } },
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
- error: ServerConfigurationError,
171
- code: 'INVALID_RATE_LIMIT_MESSAGE_TYPE',
172
- custom: true,
173
- options: { values: { received: typeof message } },
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
- error: ServerConfigurationError,
184
- code: 'INVALID_RATE_LIMIT_STANDARD_HEADERS_TYPE',
185
- custom: true,
186
- options: { values: { received: typeof standardHeaders } },
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
- error: ServerConfigurationError,
197
- code: 'INVALID_RATE_LIMIT_LEGACY_HEADERS_TYPE',
198
- custom: true,
199
- options: { values: { received: typeof legacyHeaders } },
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 {Validation} Validation result.
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
- error: ServerConfigurationError,
49
- code: 'INVALID_REQUEST_OPTIONS',
50
- custom: true,
51
- options: { values: { received: typeof options } },
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
- error: ServerConfigurationError,
62
- code: 'INVALID_BODY_LIMIT_TYPE',
63
- custom: true,
64
- options: { values: { received: typeof bodyLimit } },
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
- error: ServerConfigurationError,
71
- code: 'INVALID_BODY_LIMIT_RANGE',
72
- custom: true,
73
- options: { details: { bodyLimit } },
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
- error: ServerConfigurationError,
89
- code: 'INVALID_TRUST_PROXY_TYPE',
90
- custom: true,
91
- options: { values: { received: typeof trustProxy } },
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
- error: ServerConfigurationError,
98
- code: 'INVALID_TRUST_PROXY_RANGE',
99
- custom: true,
100
- options: { details: { trustProxy } },
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 {Validation} Validation result.
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
- error: ServerConfigurationError,
56
- code: 'INVALID_RESPONSE_OPTIONS',
57
- custom: true,
58
- options: { values: { received: typeof options } },
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
- error: ServerConfigurationError,
69
- code: 'INVALID_ETAG_TYPE',
70
- custom: true,
71
- options: { values: { received: typeof etag } },
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
- error: ServerConfigurationError,
84
- code: 'INVALID_DEFAULT_CHARSET_TYPE',
85
- custom: true,
86
- options: { values: { received: typeof defaultCharset } },
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
- error: ServerConfigurationError,
93
- code: 'INVALID_DEFAULT_CHARSET_VALUE',
94
- custom: true,
95
- options: { details: { defaultCharset } },
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
  }