@boo-labs/server 0.0.0-stage → 1.0.1
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/LICENSE +21 -0
- package/README.md +287 -2
- package/index.mjs +23 -0
- package/package.json +71 -4
- package/source/application/Kernel/index.mjs +1137 -0
- package/source/application/Server/index.mjs +1201 -0
- package/source/application/configurations/Http2CleartextServerConfiguration/index.mjs +168 -0
- package/source/application/configurations/Http2SecureServerConfiguration/index.mjs +172 -0
- package/source/application/configurations/HttpServerConfiguration/index.mjs +169 -0
- package/source/application/configurations/HttpsServerConfiguration/index.mjs +172 -0
- package/source/application/configurations/ServerConfiguration/Options/CORSOptions/CORSOptionsValue.mjs +158 -0
- package/source/application/configurations/ServerConfiguration/Options/CORSOptions/index.mjs +87 -0
- package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/CSRFOptionsValue.mjs +178 -0
- package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/index.mjs +80 -0
- package/source/application/configurations/ServerConfiguration/Options/CompressOptions/CompressOptionsValue.mjs +126 -0
- package/source/application/configurations/ServerConfiguration/Options/CompressOptions/index.mjs +81 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/CookieParserOptionsValue.mjs +107 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/index.mjs +82 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/CookieSessionOptionsValue.mjs +182 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/index.mjs +86 -0
- package/source/application/configurations/ServerConfiguration/Options/ListenOptions/ListenOptionsValue.mjs +95 -0
- package/source/application/configurations/ServerConfiguration/Options/ListenOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/MiddlewareOption.mjs +102 -0
- package/source/application/configurations/ServerConfiguration/Options/Options.mjs +105 -0
- package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/RateLimitOptionsValue.mjs +164 -0
- package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/index.mjs +84 -0
- package/source/application/configurations/ServerConfiguration/Options/RequestOptions/RequestOptionsValue.mjs +108 -0
- package/source/application/configurations/ServerConfiguration/Options/RequestOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/ResponseOptionsValue.mjs +101 -0
- package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/SecurityHeadersOptionsValue.mjs +272 -0
- package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/index.mjs +85 -0
- package/source/application/configurations/ServerConfiguration/Options/ServerOption.mjs +50 -0
- package/source/application/configurations/ServerConfiguration/Options/StaticOptions/StaticOptionsValue.mjs +164 -0
- package/source/application/configurations/ServerConfiguration/Options/StaticOptions/index.mjs +89 -0
- package/source/application/configurations/ServerConfiguration/index.mjs +781 -0
- package/source/application/resolvers/TransportResolver/index.mjs +119 -0
- package/source/application/use-cases/CloseServer/index.mjs +85 -0
- package/source/application/use-cases/ListenServer/index.mjs +98 -0
- package/source/core/EventSystem/index.mjs +110 -0
- package/source/core/Exchange/ExchangeContext.mjs +291 -0
- package/source/core/Exchange/Telemetry.mjs +193 -0
- package/source/core/Exchange/index.mjs +604 -0
- package/source/core/ExchangeLayerBuilder/index.mjs +133 -0
- package/source/core/IPCMain/IPCMain.mjs +507 -0
- package/source/core/IPCMain/IPCProtocol.mjs +441 -0
- package/source/core/IPCMain/IPCServerChannel.mjs +331 -0
- package/source/core/IPCMain/WebSocketAdapter.mjs +189 -0
- package/source/core/Request/BodyParser/BodyParser.mjs +197 -0
- package/source/core/Request/BodyParser/JSONParser.mjs +70 -0
- package/source/core/Request/BodyParser/MultipartParser.mjs +612 -0
- package/source/core/Request/BodyParser/URLEncodedParser.mjs +71 -0
- package/source/core/Request/RequestHeaders.mjs +177 -0
- package/source/core/Request/index.mjs +805 -0
- package/source/core/Response/ResponseHeaders.mjs +402 -0
- package/source/core/Response/index.mjs +1370 -0
- package/source/core/middlewares/CORSMiddleware/index.mjs +289 -0
- package/source/core/middlewares/CSRFMiddleware/index.mjs +284 -0
- package/source/core/middlewares/CompressMiddleware/index.mjs +460 -0
- package/source/core/middlewares/CookieParserMiddleware/index.mjs +194 -0
- package/source/core/middlewares/CookieSessionMiddleware/index.mjs +460 -0
- package/source/core/middlewares/RateLimitMiddleware/index.mjs +245 -0
- package/source/core/middlewares/SecurityHeadersMiddleware/index.mjs +151 -0
- package/source/core/middlewares/StaticMiddleware/index.mjs +628 -0
- package/source/core/routing/ExchangeLayer/ExchangeMatcher.mjs +127 -0
- package/source/core/routing/ExchangeLayer/index.mjs +254 -0
- package/source/core/routing/Pipeline/index.mjs +157 -0
- package/source/core/routing/Router/Route.mjs +184 -0
- package/source/core/routing/Router/index.mjs +523 -0
- package/source/core/routing/RouterRegister/OptionsMiddleware.mjs +147 -0
- package/source/core/routing/RouterRegister/index.mjs +150 -0
- package/source/core/routing/RouterSystem/index.mjs +225 -0
- package/source/core/value-objects/Address/index.mjs +281 -0
- package/source/core/value-objects/Endpoint/index.mjs +293 -0
- package/source/index.mjs +62 -0
- package/source/infrastructure/adapters/Http2Adapter/index.mjs +144 -0
- package/source/infrastructure/adapters/Http2CleartextAdapter/index.mjs +147 -0
- package/source/infrastructure/adapters/HttpAdapter/index.mjs +124 -0
- package/source/infrastructure/adapters/HttpsAdapter/index.mjs +124 -0
- package/source/infrastructure/adapters/TransportAdapter/createWebSocketOpenRequest.mjs +843 -0
- package/source/infrastructure/adapters/TransportAdapter/index.mjs +555 -0
- package/source/shared/Identifier/index.mjs +671 -0
- package/source/shared/constants/symbols/brands/index.mjs +45 -0
- package/source/shared/domain/UUIDV7/index.mjs +346 -0
- package/source/shared/errors/BaseError/index.mjs +157 -0
- package/source/shared/errors/IPCError/index.mjs +97 -0
- package/source/shared/errors/MiddlewareError/index.mjs +106 -0
- package/source/shared/errors/PathnamePatternError/index.mjs +103 -0
- package/source/shared/errors/RequestError/index.mjs +116 -0
- package/source/shared/errors/ResponseError/index.mjs +140 -0
- package/source/shared/errors/RouterError/index.mjs +90 -0
- package/source/shared/errors/ServerConfigurationError/index.mjs +551 -0
- package/source/shared/errors/ServerError/index.mjs +127 -0
- package/source/shared/errors/WebSocketError/index.mjs +58 -0
- package/source/shared/outcome/ErrorSchema.mjs +361 -0
- package/source/shared/outcome/Result/index.mjs +239 -0
- package/source/shared/outcome/Validation/index.mjs +208 -0
- package/source/shared/validations/Middleware/index.mjs +160 -0
- package/source/shared/validations/PathnamePattern/index.mjs +256 -0
- package/source/shared/validations/Server/NotFoundHandler/index.mjs +94 -0
- package/source/shared/validations/Server/Router/index.mjs +68 -0
- package/source/shared/validations/Server/ServerConfiguration/index.mjs +68 -0
- package/source/shared/validations/Server/SystemErrorHandler/index.mjs +94 -0
- package/source/shared/validations/ServerConfiguration/CORSOptions/index.mjs +310 -0
- package/source/shared/validations/ServerConfiguration/CSRFOptions/index.mjs +299 -0
- package/source/shared/validations/ServerConfiguration/CompressOptions/index.mjs +204 -0
- package/source/shared/validations/ServerConfiguration/CookieParserOptions/index.mjs +102 -0
- package/source/shared/validations/ServerConfiguration/CookieSessionOptions/index.mjs +332 -0
- package/source/shared/validations/ServerConfiguration/ListenOptions/index.mjs +178 -0
- package/source/shared/validations/ServerConfiguration/RateLimitOptions/index.mjs +229 -0
- package/source/shared/validations/ServerConfiguration/RequestOptions/index.mjs +132 -0
- package/source/shared/validations/ServerConfiguration/ResponseOptions/index.mjs +122 -0
- package/source/shared/validations/ServerConfiguration/SecurityHeadersOptions/index.mjs +137 -0
- package/source/shared/validations/ServerConfiguration/StaticOptions/index.mjs +248 -0
- package/types/index.d.ts +719 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Core validation module for representing strict validation outcomes.
|
|
4
|
+
*
|
|
5
|
+
* @description Provides the {@link Validation} class for explicitly representing
|
|
6
|
+
* successful and failed operations without relying on
|
|
7
|
+
* ambiguous `undefined` states.
|
|
8
|
+
*
|
|
9
|
+
* @module Validation
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
13
|
+
// » IMPORT LOCAL MODULES
|
|
14
|
+
import ErrorSchema from '../ErrorSchema.mjs';
|
|
15
|
+
|
|
16
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* Represents an Error constructor.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {new (identifier: string, options?: Record<string, unknown>) => Error} ErrorConstructor
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Represents options passed to a native Error constructor.
|
|
25
|
+
*
|
|
26
|
+
* @typedef {object} NativeErrorOptions
|
|
27
|
+
* @property {unknown} [cause] - Cause associated with the error.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Represents a native Error schema definition.
|
|
32
|
+
*
|
|
33
|
+
* @typedef {object} NativeErrorSchema
|
|
34
|
+
* @property {ErrorConstructor} error - Error constructor used to create the error.
|
|
35
|
+
* @property {string} message - Literal error message.
|
|
36
|
+
* @property {NativeErrorOptions} [options] - Options passed to the Error constructor.
|
|
37
|
+
* @property {false} custom - Indicates that the constructor represents a native Error.
|
|
38
|
+
* @property {string} code - Error identifier.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Represents options passed to a custom Error constructor.
|
|
43
|
+
*
|
|
44
|
+
* @typedef {object} CustomErrorOptions
|
|
45
|
+
* @property {Record<string, unknown>} [values] - Values used to resolve message placeholders.
|
|
46
|
+
* @property {Record<string, unknown>} [details] - Additional contextual information about the error.
|
|
47
|
+
* @property {unknown} [cause] - Cause associated with the error.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Represents a custom Error schema definition.
|
|
52
|
+
*
|
|
53
|
+
* @typedef {object} CustomErrorSchema
|
|
54
|
+
* @property {ErrorConstructor} error - Error constructor used to create the error.
|
|
55
|
+
* @property {string} code - Error code used to resolve the error message and error identifier.
|
|
56
|
+
* @property {string} [message] - Literal error message.
|
|
57
|
+
* @property {CustomErrorOptions} [options] - Options passed to the Error constructor.
|
|
58
|
+
* @property {true} custom - Indicates that the constructor represents a custom Error.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Represents a supported Error schema definition.
|
|
63
|
+
*
|
|
64
|
+
* @typedef {NativeErrorSchema|CustomErrorSchema} ErrorSchemaDefinition
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Represents the successful state of a {@link Validation} instance.
|
|
69
|
+
*
|
|
70
|
+
* @typedef {object} ValidationStateSuccess
|
|
71
|
+
* @property {true} ok - Indicates that the validation succeeded.
|
|
72
|
+
* @property {undefined} [error] - Always undefined for a successful validation.
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Represents the failed state of a {@link Validation} instance.
|
|
77
|
+
*
|
|
78
|
+
* @typedef {object} ValidationStateFailure
|
|
79
|
+
* @property {false} ok - Indicates that the validation failed.
|
|
80
|
+
* @property {ErrorSchema} error - Error schema describing the validation failure.
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Represents the internal state of a {@link Validation} instance.
|
|
85
|
+
*
|
|
86
|
+
* @typedef {ValidationStateSuccess|ValidationStateFailure} ValidationState
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
90
|
+
/**
|
|
91
|
+
* Represents the strict outcome of a validation operation (pass/fail).
|
|
92
|
+
*
|
|
93
|
+
* Unlike Result, it does not carry a successful payload, only an error on failure.
|
|
94
|
+
*/
|
|
95
|
+
class Validation {
|
|
96
|
+
/**
|
|
97
|
+
* Internal validation state.
|
|
98
|
+
*
|
|
99
|
+
* @type {ValidationState}
|
|
100
|
+
*/
|
|
101
|
+
#validation;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Creates a Validation instance.
|
|
105
|
+
*
|
|
106
|
+
* @param {boolean} ok - Whether the validation succeeded.
|
|
107
|
+
* @param {ErrorSchemaDefinition} [error] - Required when ok is false.
|
|
108
|
+
* @throws {TypeError} If the combination of ok and error is invalid.
|
|
109
|
+
* @example
|
|
110
|
+
* ```js
|
|
111
|
+
* const validation = new Validation(true);
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```js
|
|
116
|
+
* const validation = new Validation(false, {
|
|
117
|
+
* error: TypeError,
|
|
118
|
+
* message: 'Expected a string.',
|
|
119
|
+
* code: 'INVALID_TYPE',
|
|
120
|
+
* });
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
constructor(ok, error) {
|
|
124
|
+
if (typeof ok !== 'boolean') {
|
|
125
|
+
throw new TypeError('Validation status (ok) must be a boolean.');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (ok === true) {
|
|
129
|
+
if (error !== undefined) {
|
|
130
|
+
throw new TypeError('A successful Validation cannot contain an error.');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
this.#validation = { ok, error };
|
|
134
|
+
} else {
|
|
135
|
+
if (error === undefined) {
|
|
136
|
+
throw new TypeError('A failed Validation must contain an error.');
|
|
137
|
+
}
|
|
138
|
+
this.#validation = { ok, error: new ErrorSchema(error) };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Indicates whether the validation succeeded.
|
|
144
|
+
*
|
|
145
|
+
* @readonly
|
|
146
|
+
* @type {boolean}
|
|
147
|
+
*/
|
|
148
|
+
get ok() {
|
|
149
|
+
return this.#validation.ok;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The instantiated error of the failed validation.
|
|
154
|
+
*
|
|
155
|
+
* @readonly
|
|
156
|
+
* @type {Error}
|
|
157
|
+
* @throws {Error} If the validation succeeded.
|
|
158
|
+
*/
|
|
159
|
+
get error() {
|
|
160
|
+
if (this.#validation.ok === true) {
|
|
161
|
+
throw new Error('Cannot access error of a successful Validation.');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return this.#validation.error.error;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Creates a successful {@link Validation}.
|
|
169
|
+
*
|
|
170
|
+
* @returns {Validation} A successful Validation instance.
|
|
171
|
+
* @example
|
|
172
|
+
* ```js
|
|
173
|
+
* const validation = Validation.success();
|
|
174
|
+
*
|
|
175
|
+
* if (validation.ok) {
|
|
176
|
+
* console.log('Validation passed.');
|
|
177
|
+
* }
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
static success() {
|
|
181
|
+
return new Validation(true);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Creates a failed {@link Validation}.
|
|
186
|
+
*
|
|
187
|
+
* @param {ErrorSchemaDefinition} error - The error details describing the failure.
|
|
188
|
+
* @returns {Validation} A failed Validation instance.
|
|
189
|
+
* @example
|
|
190
|
+
* ```js
|
|
191
|
+
* const validation = Validation.failure({
|
|
192
|
+
* error: RangeError,
|
|
193
|
+
* message: 'Value is out of range.',
|
|
194
|
+
* code: 'VALUE_OUT_OF_RANGE',
|
|
195
|
+
* });
|
|
196
|
+
*
|
|
197
|
+
* if (!validation.ok) {
|
|
198
|
+
* console.log(validation.error);
|
|
199
|
+
* }
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
static failure(error) {
|
|
203
|
+
return new Validation(false, error);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
208
|
+
export default Validation;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Middleware validation utilities.
|
|
4
|
+
*
|
|
5
|
+
* @description Validates middleware functions used by the HTTP server.
|
|
6
|
+
*
|
|
7
|
+
* @module Validation/Middleware
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
11
|
+
// » IMPORT LOCAL MODULES
|
|
12
|
+
import Validation from '#shared/outcome/Validation';
|
|
13
|
+
import MiddlewareError from '#shared/errors/MiddlewareError';
|
|
14
|
+
|
|
15
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
16
|
+
/**
|
|
17
|
+
* Validates a middleware function.
|
|
18
|
+
*
|
|
19
|
+
* A middleware must be a function that declares exactly two parameters:
|
|
20
|
+
* `context` and `next`.
|
|
21
|
+
*
|
|
22
|
+
* @param {unknown} middleware - Middleware function to validate.
|
|
23
|
+
* @returns {Validation} Validation result.
|
|
24
|
+
* @example
|
|
25
|
+
* ```js
|
|
26
|
+
* const validation = validateMiddleware((context, next) => {
|
|
27
|
+
* next();
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* console.log(validation.ok);
|
|
31
|
+
* // => true
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```js
|
|
36
|
+
* const validation = validateMiddleware((context) => {
|
|
37
|
+
* return context;
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* console.log(validation.ok);
|
|
41
|
+
* // => false
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function validateMiddleware(middleware) {
|
|
45
|
+
if (typeof middleware !== 'function') {
|
|
46
|
+
return Validation.failure({
|
|
47
|
+
error: MiddlewareError,
|
|
48
|
+
code: 'INVALID_MIDDLEWARE_TYPE',
|
|
49
|
+
custom: true,
|
|
50
|
+
options: {
|
|
51
|
+
values: {
|
|
52
|
+
expected: 'function',
|
|
53
|
+
received: typeof middleware,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (middleware.length !== 2) {
|
|
60
|
+
return Validation.failure({
|
|
61
|
+
error: MiddlewareError,
|
|
62
|
+
code: 'INVALID_MIDDLEWARE_ARITY',
|
|
63
|
+
custom: true,
|
|
64
|
+
options: {
|
|
65
|
+
values: {
|
|
66
|
+
expected: 2,
|
|
67
|
+
received: middleware.length,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return Validation.success();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Asserts that a middleware function is valid.
|
|
78
|
+
*
|
|
79
|
+
* @param {unknown} middleware - Middleware function to validate.
|
|
80
|
+
* @throws {MiddlewareError} If the middleware is not a function.
|
|
81
|
+
* @throws {MiddlewareError} If the middleware does not declare exactly two parameters.
|
|
82
|
+
* @example
|
|
83
|
+
* ```js
|
|
84
|
+
* assertMiddleware((context, next) => {
|
|
85
|
+
* next();
|
|
86
|
+
* });
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export function assertMiddleware(middleware) {
|
|
90
|
+
const validation = validateMiddleware(middleware);
|
|
91
|
+
|
|
92
|
+
if (!validation.ok) {
|
|
93
|
+
throw validation.error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Validates an array of middleware functions.
|
|
99
|
+
*
|
|
100
|
+
* @param {unknown} middlewares - Middleware functions to validate.
|
|
101
|
+
* @returns {Validation} Validation result.
|
|
102
|
+
* @example
|
|
103
|
+
* ```js
|
|
104
|
+
* const validation = validateMiddlewares([
|
|
105
|
+
* (context, next) => next(),
|
|
106
|
+
* (context, next) => next(),
|
|
107
|
+
* ]);
|
|
108
|
+
*
|
|
109
|
+
* console.log(validation.ok);
|
|
110
|
+
* // => true
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function validateMiddlewares(middlewares) {
|
|
114
|
+
if (!Array.isArray(middlewares)) {
|
|
115
|
+
return Validation.failure({
|
|
116
|
+
error: MiddlewareError,
|
|
117
|
+
code: 'INVALID_MIDDLEWARES_TYPE',
|
|
118
|
+
custom: true,
|
|
119
|
+
options: {
|
|
120
|
+
values: {
|
|
121
|
+
expected: 'array',
|
|
122
|
+
received: typeof middlewares,
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const middleware of middlewares) {
|
|
129
|
+
const validation = validateMiddleware(middleware);
|
|
130
|
+
|
|
131
|
+
if (!validation.ok) {
|
|
132
|
+
return validation;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return Validation.success();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Asserts that all middleware functions are valid.
|
|
141
|
+
*
|
|
142
|
+
* @param {unknown} middlewares - Middleware functions to validate.
|
|
143
|
+
* @throws {MiddlewareError} If the middleware collection is not an array.
|
|
144
|
+
* @throws {MiddlewareError} If a middleware is not a function.
|
|
145
|
+
* @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
|
|
146
|
+
* @example
|
|
147
|
+
* ```js
|
|
148
|
+
* assertMiddlewares([
|
|
149
|
+
* (context, next) => next(),
|
|
150
|
+
* (context, next) => next(),
|
|
151
|
+
* ]);
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
export function assertMiddlewares(middlewares) {
|
|
155
|
+
const validation = validateMiddlewares(middlewares);
|
|
156
|
+
|
|
157
|
+
if (!validation.ok) {
|
|
158
|
+
throw validation.error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Pathname pattern validation utilities.
|
|
4
|
+
*
|
|
5
|
+
* @description Validates pathname patterns used by the HTTP router, including
|
|
6
|
+
* parameter syntax, wildcard segments, length constraints, and duplicate
|
|
7
|
+
* parameter names.
|
|
8
|
+
*
|
|
9
|
+
* @module Validation/PathnamePattern
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
13
|
+
// » IMPORT LOCAL MODULES
|
|
14
|
+
import Validation from '#shared/outcome/Validation';
|
|
15
|
+
import PathnamePatternError from '#shared/errors/PathnamePatternError';
|
|
16
|
+
|
|
17
|
+
// ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
18
|
+
/**
|
|
19
|
+
* Maximum number of characters allowed in a pathname pattern.
|
|
20
|
+
*
|
|
21
|
+
* @private
|
|
22
|
+
* @type {number}
|
|
23
|
+
*/
|
|
24
|
+
const MAX_PATTERN_LENGTH = 512;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Regular expression source for a valid pathname parameter name.
|
|
28
|
+
*
|
|
29
|
+
* Parameter names must begin with a letter or underscore and may contain
|
|
30
|
+
* letters, digits, and underscores.
|
|
31
|
+
*
|
|
32
|
+
* @private
|
|
33
|
+
* @type {string}
|
|
34
|
+
*/
|
|
35
|
+
const PARAM_NAME = String.raw`[A-Za-z_][A-Za-z0-9_]*`;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Regular expression source for a literal character allowed inside a pathname
|
|
39
|
+
* segment.
|
|
40
|
+
*
|
|
41
|
+
* @private
|
|
42
|
+
* @type {string}
|
|
43
|
+
*/
|
|
44
|
+
const LITERAL_CHAR = String.raw`[A-Za-z0-9._~!$&'()+,;=@-]`;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Regular expression source for a parameter segment using colon or brace
|
|
48
|
+
* syntax.
|
|
49
|
+
*
|
|
50
|
+
* Supported forms are `:name` and `{name}`.
|
|
51
|
+
*
|
|
52
|
+
* @private
|
|
53
|
+
* @type {string}
|
|
54
|
+
*/
|
|
55
|
+
const PARAM_SEGMENT = String.raw`(?::${PARAM_NAME}|\{${PARAM_NAME}\})`;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Regular expression source for a normal pathname segment.
|
|
59
|
+
*
|
|
60
|
+
* A normal segment may contain literal characters and parameter segments, but
|
|
61
|
+
* cannot represent `.` or `..` as complete path segments.
|
|
62
|
+
*
|
|
63
|
+
* @private
|
|
64
|
+
* @type {string}
|
|
65
|
+
*/
|
|
66
|
+
const NORMAL_SEGMENT = String.raw`(?!\.{1,2}(?=/|$))(?:${LITERAL_CHAR}|${PARAM_SEGMENT})+`;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Regular expression source for a wildcard pathname segment.
|
|
70
|
+
*
|
|
71
|
+
* @private
|
|
72
|
+
* @type {string}
|
|
73
|
+
*/
|
|
74
|
+
const WILDCARD_SEGMENT = String.raw`\*`;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Complete regular expression source for supported pathname patterns.
|
|
78
|
+
*
|
|
79
|
+
* The expression supports the global wildcard `*` and pathname patterns
|
|
80
|
+
* beginning with `/`, including parameter and trailing wildcard segments.
|
|
81
|
+
*
|
|
82
|
+
* @private
|
|
83
|
+
* @type {string}
|
|
84
|
+
*/
|
|
85
|
+
const PATHNAME_PATTERN_SEGMENT = String.raw`^(?:\*|/(?:${NORMAL_SEGMENT}(?:/${NORMAL_SEGMENT})*)?(?:/${WILDCARD_SEGMENT})?)$`;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Regular expression used to validate pathname pattern syntax.
|
|
89
|
+
*
|
|
90
|
+
* @private
|
|
91
|
+
* @type {RegExp}
|
|
92
|
+
*/
|
|
93
|
+
const PATHNAME_PATTERN_REGEXP = new RegExp(PATHNAME_PATTERN_SEGMENT);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Regular expression used to extract pathname parameter names.
|
|
97
|
+
*
|
|
98
|
+
* Both `:name` and `{name}` parameter syntaxes are supported.
|
|
99
|
+
*
|
|
100
|
+
* @private
|
|
101
|
+
* @type {RegExp}
|
|
102
|
+
*/
|
|
103
|
+
const PARAMETER_PATTERN_REGEXP = /:([A-Za-z_][A-Za-z0-9_]*)|\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
104
|
+
|
|
105
|
+
// ━━ FUNCTIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
106
|
+
/**
|
|
107
|
+
* Validates that all parameter names in a pathname pattern are unique.
|
|
108
|
+
*
|
|
109
|
+
* Parameter names are compared by their literal names regardless of whether
|
|
110
|
+
* they use colon or brace syntax.
|
|
111
|
+
*
|
|
112
|
+
* @private
|
|
113
|
+
* @param {string} pathnamePattern - Pathname pattern to validate.
|
|
114
|
+
* @returns {Validation} Validation result.
|
|
115
|
+
* @example
|
|
116
|
+
* ```js
|
|
117
|
+
* const validation = validateUniqueParamNames('/users/:id/posts/{postId}');
|
|
118
|
+
*
|
|
119
|
+
* console.log(validation.ok);
|
|
120
|
+
* // => true
|
|
121
|
+
* ```
|
|
122
|
+
* @example
|
|
123
|
+
* ```js
|
|
124
|
+
* const validation = validateUniqueParamNames('/users/:id/posts/{id}');
|
|
125
|
+
*
|
|
126
|
+
* console.log(validation.ok);
|
|
127
|
+
* // => false
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
function validateUniqueParamNames(pathnamePattern) {
|
|
131
|
+
const names = new Set();
|
|
132
|
+
|
|
133
|
+
for (const match of pathnamePattern.matchAll(PARAMETER_PATTERN_REGEXP)) {
|
|
134
|
+
const name = match[1] ?? match[2];
|
|
135
|
+
|
|
136
|
+
if (names.has(name)) {
|
|
137
|
+
return Validation.failure({
|
|
138
|
+
error: PathnamePatternError,
|
|
139
|
+
code: 'DUPLICATE_PARAMETER_NAME',
|
|
140
|
+
custom: true,
|
|
141
|
+
options: {
|
|
142
|
+
values: { name },
|
|
143
|
+
details: { pathnamePattern },
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
names.add(name);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return Validation.success();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
155
|
+
/**
|
|
156
|
+
* Validates a pathname pattern against the supported pathname syntax and
|
|
157
|
+
* semantic constraints.
|
|
158
|
+
*
|
|
159
|
+
* Validation is performed in the following order:
|
|
160
|
+
*
|
|
161
|
+
* 1. The pathname pattern must be a string.
|
|
162
|
+
* 2. The pathname pattern must not be empty.
|
|
163
|
+
* 3. The pathname pattern must not exceed the maximum allowed length.
|
|
164
|
+
* 4. The pathname pattern must match the supported pathname syntax.
|
|
165
|
+
* 5. Parameter names must be unique.
|
|
166
|
+
*
|
|
167
|
+
* @param {unknown} pathnamePattern - Pathname pattern to validate.
|
|
168
|
+
* @returns {Validation} Validation result.
|
|
169
|
+
* @example
|
|
170
|
+
* ```js
|
|
171
|
+
* const validation = validatePathnamePattern('/users/:id');
|
|
172
|
+
*
|
|
173
|
+
* console.log(validation.ok);
|
|
174
|
+
* // => true
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```js
|
|
179
|
+
* const validation = validatePathnamePattern('/users/:id/:id');
|
|
180
|
+
*
|
|
181
|
+
* console.log(validation.ok);
|
|
182
|
+
* // => false
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
export function validatePathnamePattern(pathnamePattern) {
|
|
186
|
+
if (typeof pathnamePattern !== 'string') {
|
|
187
|
+
return Validation.failure({
|
|
188
|
+
error: PathnamePatternError,
|
|
189
|
+
code: 'INVALID_PATTERN_TYPE',
|
|
190
|
+
custom: true,
|
|
191
|
+
options: {
|
|
192
|
+
values: { expected: 'string', received: typeof pathnamePattern },
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (pathnamePattern.length === 0) {
|
|
198
|
+
return Validation.failure({
|
|
199
|
+
error: PathnamePatternError,
|
|
200
|
+
code: 'EMPTY_PATTERN',
|
|
201
|
+
custom: true,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (pathnamePattern.length > MAX_PATTERN_LENGTH) {
|
|
206
|
+
return Validation.failure({
|
|
207
|
+
error: PathnamePatternError,
|
|
208
|
+
code: 'PATTERN_TOO_LONG',
|
|
209
|
+
custom: true,
|
|
210
|
+
options: {
|
|
211
|
+
values: { maxLength: MAX_PATTERN_LENGTH, actualLength: pathnamePattern.length },
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!PATHNAME_PATTERN_REGEXP.test(pathnamePattern)) {
|
|
217
|
+
return Validation.failure({
|
|
218
|
+
error: PathnamePatternError,
|
|
219
|
+
code: 'INVALID_PATHNAME_PATTERN',
|
|
220
|
+
custom: true,
|
|
221
|
+
options: {
|
|
222
|
+
values: { pathnamePattern },
|
|
223
|
+
details: { constraint: 'Must be * or start with / and contain valid segments' },
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return validateUniqueParamNames(pathnamePattern);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Asserts that a pathname pattern is valid.
|
|
233
|
+
*
|
|
234
|
+
* @param {unknown} pathnamePattern - Pathname pattern to validate.
|
|
235
|
+
* @throws {PathnamePatternError} If the pathname pattern is not a string.
|
|
236
|
+
* @throws {PathnamePatternError} If the pathname pattern is empty.
|
|
237
|
+
* @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
|
|
238
|
+
* @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
|
|
239
|
+
* @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
|
|
240
|
+
* @example
|
|
241
|
+
* ```js
|
|
242
|
+
* assertPathnamePattern('/users/:id');
|
|
243
|
+
* ```
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```js
|
|
247
|
+
* assertPathnamePattern('/users/:id/:id');
|
|
248
|
+
* // throws PathnamePatternError with code 'DUPLICATE_PARAMETER_NAME'
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
export function assertPathnamePattern(pathnamePattern) {
|
|
252
|
+
const validation = validatePathnamePattern(pathnamePattern);
|
|
253
|
+
if (!validation.ok) {
|
|
254
|
+
throw validation.error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file NotFoundHandler validation utilities.
|
|
4
|
+
*
|
|
5
|
+
* @description Validates handlers used to resolve requests that do not match
|
|
6
|
+
* any registered route.
|
|
7
|
+
*
|
|
8
|
+
* @module Validation/Server/NotFoundHandler
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
12
|
+
// » IMPORT LOCAL MODULES
|
|
13
|
+
import Validation from '#shared/outcome/Validation';
|
|
14
|
+
import ServerError from '#shared/errors/ServerError';
|
|
15
|
+
|
|
16
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* Validates a not-found handler.
|
|
19
|
+
*
|
|
20
|
+
* A not-found handler must be a function that declares exactly one parameter:
|
|
21
|
+
* `exchange`.
|
|
22
|
+
*
|
|
23
|
+
* @param {unknown} handler - Handler function to validate.
|
|
24
|
+
* @returns {Validation} Validation result.
|
|
25
|
+
* @example
|
|
26
|
+
* ```js
|
|
27
|
+
* const validation = validateNotFoundHandler((exchange) => {
|
|
28
|
+
* exchange.response.status(404).end();
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* console.log(validation.ok);
|
|
32
|
+
* // => true
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```js
|
|
37
|
+
* const validation = validateNotFoundHandler(() => {});
|
|
38
|
+
*
|
|
39
|
+
* console.log(validation.ok);
|
|
40
|
+
* // => false
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export function validateNotFoundHandler(handler) {
|
|
44
|
+
if (typeof handler !== 'function') {
|
|
45
|
+
return Validation.failure({
|
|
46
|
+
error: ServerError,
|
|
47
|
+
code: 'INVALID_NOT_FOUND_HANDLER_TYPE',
|
|
48
|
+
custom: true,
|
|
49
|
+
options: {
|
|
50
|
+
values: {
|
|
51
|
+
expected: 'function',
|
|
52
|
+
received: typeof handler,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (handler.length !== 1) {
|
|
59
|
+
return Validation.failure({
|
|
60
|
+
error: ServerError,
|
|
61
|
+
code: 'INVALID_NOT_FOUND_HANDLER_ARITY',
|
|
62
|
+
custom: true,
|
|
63
|
+
options: {
|
|
64
|
+
values: {
|
|
65
|
+
expected: 1,
|
|
66
|
+
received: handler.length,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Validation.success();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Asserts that a not-found handler is valid.
|
|
77
|
+
*
|
|
78
|
+
* @param {unknown} handler - Handler function to validate.
|
|
79
|
+
* @throws {ServerError} If the handler is not a function.
|
|
80
|
+
* @throws {ServerError} If the handler does not declare exactly one parameter.
|
|
81
|
+
* @example
|
|
82
|
+
* ```js
|
|
83
|
+
* assertNotFoundHandler((exchange) => {
|
|
84
|
+
* exchange.response.status(404).end();
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export function assertNotFoundHandler(handler) {
|
|
89
|
+
const validation = validateNotFoundHandler(handler);
|
|
90
|
+
|
|
91
|
+
if (!validation.ok) {
|
|
92
|
+
throw validation.error;
|
|
93
|
+
}
|
|
94
|
+
}
|