@stacksjs/bun-router 0.0.13 → 0.0.14
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/CHANGELOG.md +770 -0
- package/README.md +584 -261
- package/package.json +43 -47
- package/bin/cli.ts +0 -9
- package/dist/auth.d.ts +0 -134
- package/dist/cache/lru-cache.d.ts +0 -139
- package/dist/cache/middleware-memoization.d.ts +0 -151
- package/dist/cache/route-cache-warmer.d.ts +0 -161
- package/dist/cache/sqlite-cache.d.ts +0 -209
- package/dist/cache/streaming-cache.d.ts +0 -132
- package/dist/chunk-1mvtv6jw.js +0 -13012
- package/dist/cli/colors.d.ts +0 -15
- package/dist/cli/index.d.ts +0 -10
- package/dist/cli/middleware.d.ts +0 -31
- package/dist/cli/openapi.d.ts +0 -17
- package/dist/cli/router.d.ts +0 -15
- package/dist/cli/routes.d.ts +0 -29
- package/dist/cli/utils.d.ts +0 -74
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -3195
- package/dist/config.d.ts +0 -7
- package/dist/container/container.d.ts +0 -273
- package/dist/container/contextual-binding.d.ts +0 -240
- package/dist/container/decorators.d.ts +0 -141
- package/dist/container/service-provider.d.ts +0 -285
- package/dist/development/hot-reload.d.ts +0 -187
- package/dist/development/index.d.ts +0 -167
- package/dist/development/performance-profiler.d.ts +0 -217
- package/dist/development/route-debugger.d.ts +0 -154
- package/dist/development/route-inspector.d.ts +0 -211
- package/dist/development/typescript-utilities.d.ts +0 -209
- package/dist/docs.d.ts +0 -11
- package/dist/errors/circuit-breaker.d.ts +0 -195
- package/dist/errors/error-handler.d.ts +0 -88
- package/dist/errors/error-reporting.d.ts +0 -144
- package/dist/errors/exceptions.d.ts +0 -184
- package/dist/errors/graceful-degradation.d.ts +0 -154
- package/dist/errors/index.d.ts +0 -9
- package/dist/errors/router-errors.d.ts +0 -110
- package/dist/file-serving/static-files.d.ts +0 -143
- package/dist/index.d.ts +0 -14
- package/dist/index.js +0 -13933
- package/dist/middleware/auth.d.ts +0 -63
- package/dist/middleware/content_security_policy.d.ts +0 -53
- package/dist/middleware/cors.d.ts +0 -5
- package/dist/middleware/csrf.d.ts +0 -7
- package/dist/middleware/ddos_protection.d.ts +0 -39
- package/dist/middleware/file_security.d.ts +0 -26
- package/dist/middleware/file_upload.d.ts +0 -38
- package/dist/middleware/helmet.d.ts +0 -54
- package/dist/middleware/index.d.ts +0 -44
- package/dist/middleware/input_validation.d.ts +0 -45
- package/dist/middleware/json_body.d.ts +0 -4
- package/dist/middleware/performance_alerting.d.ts +0 -87
- package/dist/middleware/performance_dashboard.d.ts +0 -87
- package/dist/middleware/performance_monitor.d.ts +0 -209
- package/dist/middleware/pipeline.d.ts +0 -131
- package/dist/middleware/rate_limit.d.ts +0 -36
- package/dist/middleware/request_id.d.ts +0 -4
- package/dist/middleware/request_signing.d.ts +0 -153
- package/dist/middleware/request_tracer.d.ts +0 -72
- package/dist/middleware/response_cache.d.ts +0 -97
- package/dist/middleware/security.d.ts +0 -69
- package/dist/middleware/security_suite.d.ts +0 -46
- package/dist/middleware/session.d.ts +0 -23
- package/dist/model-binding/index.d.ts +0 -2
- package/dist/model-binding/model-middleware.d.ts +0 -118
- package/dist/model-binding/model-registry.d.ts +0 -164
- package/dist/model-binding.d.ts +0 -185
- package/dist/model-resolver-factory.d.ts +0 -31
- package/dist/observability/correlation.d.ts +0 -187
- package/dist/observability/health-checks.d.ts +0 -186
- package/dist/observability/index.d.ts +0 -60
- package/dist/observability/integration.d.ts +0 -147
- package/dist/observability/metrics.d.ts +0 -184
- package/dist/observability/tracing.d.ts +0 -187
- package/dist/optimization/bun-utilities.d.ts +0 -224
- package/dist/query-builder-integration.d.ts +0 -36
- package/dist/request/context.d.ts +0 -34
- package/dist/request/enhanced-request.d.ts +0 -213
- package/dist/request/macros.d.ts +0 -300
- package/dist/response/macros.d.ts +0 -245
- package/dist/response/response-factory.d.ts +0 -133
- package/dist/router/api-routes.d.ts +0 -5
- package/dist/router/file-based-routing.d.ts +0 -118
- package/dist/router/file-streaming.d.ts +0 -5
- package/dist/router/fluent-router.d.ts +0 -315
- package/dist/router/fluent-routing.d.ts +0 -271
- package/dist/router/group-organization.d.ts +0 -5
- package/dist/router/handler-resolver.d.ts +0 -24
- package/dist/router/http-methods.d.ts +0 -10
- package/dist/router/index.d.ts +0 -114
- package/dist/router/middleware-groups.d.ts +0 -94
- package/dist/router/middleware-integration.d.ts +0 -112
- package/dist/router/middleware.d.ts +0 -5
- package/dist/router/model-binding.d.ts +0 -5
- package/dist/router/optimized-route-matching.d.ts +0 -6
- package/dist/router/route-building.d.ts +0 -5
- package/dist/router/route-compiler.d.ts +0 -153
- package/dist/router/route-matching.d.ts +0 -5
- package/dist/router/route-trie.d.ts +0 -114
- package/dist/router/router.d.ts +0 -294
- package/dist/router/server.d.ts +0 -5
- package/dist/router/validation-integration.d.ts +0 -170
- package/dist/router/view-rendering.d.ts +0 -5
- package/dist/router/websocket.d.ts +0 -5
- package/dist/routing/route-caching.d.ts +0 -129
- package/dist/routing/route-throttling.d.ts +0 -150
- package/dist/routing/subdomain-routing.d.ts +0 -206
- package/dist/session/database-store.d.ts +0 -51
- package/dist/session/file-store.d.ts +0 -22
- package/dist/session/index.d.ts +0 -106
- package/dist/session/memory-store.d.ts +0 -21
- package/dist/session/redis-store.d.ts +0 -31
- package/dist/streaming/index.d.ts +0 -3
- package/dist/streaming/sse-handler.d.ts +0 -138
- package/dist/streaming/stream-handler.d.ts +0 -114
- package/dist/testing/auth-testing.d.ts +0 -156
- package/dist/testing/file-upload-testing.d.ts +0 -190
- package/dist/testing/index.d.ts +0 -10
- package/dist/testing/middleware-testing.d.ts +0 -138
- package/dist/testing/model-binding-testing.d.ts +0 -186
- package/dist/testing/performance-testing.d.ts +0 -235
- package/dist/testing/test-client.d.ts +0 -117
- package/dist/testing/test-request.d.ts +0 -85
- package/dist/testing/test-response.d.ts +0 -90
- package/dist/testing/types.d.ts +0 -207
- package/dist/testing/websocket-testing.d.ts +0 -227
- package/dist/types/controller-types.d.ts +0 -208
- package/dist/types/core.d.ts +0 -543
- package/dist/types/middleware-types.d.ts +0 -227
- package/dist/types/request-response-augmentation.d.ts +0 -261
- package/dist/types/route-inference.d.ts +0 -168
- package/dist/types.d.ts +0 -1782
- package/dist/url.d.ts +0 -56
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/query-preservation.d.ts +0 -48
- package/dist/utils.d.ts +0 -69
- package/dist/validation/validator.d.ts +0 -140
- package/dist/websocket/clustering.d.ts +0 -185
- package/src/auth.ts +0 -469
- package/src/cache/lru-cache.ts +0 -457
- package/src/cache/middleware-memoization.ts +0 -531
- package/src/cache/route-cache-warmer.ts +0 -486
- package/src/cache/sqlite-cache.ts +0 -783
- package/src/cache/streaming-cache.ts +0 -572
- package/src/cli/colors.ts +0 -29
- package/src/cli/index.ts +0 -287
- package/src/cli/middleware.ts +0 -291
- package/src/cli/openapi.ts +0 -407
- package/src/cli/router.ts +0 -188
- package/src/cli/routes.ts +0 -265
- package/src/cli/utils.ts +0 -531
- package/src/cli.ts +0 -5
- package/src/config.ts +0 -322
- package/src/container/container.ts +0 -740
- package/src/container/contextual-binding.ts +0 -603
- package/src/container/decorators.ts +0 -359
- package/src/container/service-provider.ts +0 -596
- package/src/development/hot-reload.ts +0 -673
- package/src/development/index.ts +0 -499
- package/src/development/performance-profiler.ts +0 -717
- package/src/development/route-debugger.ts +0 -527
- package/src/development/route-inspector.ts +0 -749
- package/src/development/typescript-utilities.ts +0 -682
- package/src/docs.ts +0 -397
- package/src/errors/circuit-breaker.ts +0 -732
- package/src/errors/error-handler.ts +0 -569
- package/src/errors/error-reporting.ts +0 -672
- package/src/errors/exceptions.ts +0 -536
- package/src/errors/graceful-degradation.ts +0 -621
- package/src/errors/index.ts +0 -21
- package/src/errors/router-errors.ts +0 -632
- package/src/file-serving/static-files.ts +0 -581
- package/src/index.ts +0 -14
- package/src/middleware/auth.ts +0 -219
- package/src/middleware/content_security_policy.ts +0 -215
- package/src/middleware/cors.ts +0 -74
- package/src/middleware/csrf.ts +0 -108
- package/src/middleware/ddos_protection.ts +0 -255
- package/src/middleware/file_security.ts +0 -191
- package/src/middleware/file_upload.ts +0 -264
- package/src/middleware/helmet.ts +0 -268
- package/src/middleware/index.ts +0 -117
- package/src/middleware/input_validation.ts +0 -449
- package/src/middleware/json_body.ts +0 -37
- package/src/middleware/performance_alerting.ts +0 -538
- package/src/middleware/performance_dashboard.ts +0 -661
- package/src/middleware/performance_monitor.ts +0 -943
- package/src/middleware/pipeline.ts +0 -489
- package/src/middleware/rate_limit.ts +0 -245
- package/src/middleware/request_id.ts +0 -36
- package/src/middleware/request_signing.ts +0 -636
- package/src/middleware/request_tracer.ts +0 -636
- package/src/middleware/response_cache.ts +0 -743
- package/src/middleware/security.ts +0 -482
- package/src/middleware/security_suite.ts +0 -257
- package/src/middleware/session.ts +0 -91
- package/src/model-binding/index.ts +0 -18
- package/src/model-binding/model-middleware.ts +0 -425
- package/src/model-binding/model-registry.ts +0 -550
- package/src/model-binding.ts +0 -370
- package/src/model-resolver-factory.ts +0 -106
- package/src/observability/correlation.ts +0 -691
- package/src/observability/health-checks.ts +0 -729
- package/src/observability/index.ts +0 -184
- package/src/observability/integration.ts +0 -548
- package/src/observability/metrics.ts +0 -753
- package/src/observability/tracing.ts +0 -638
- package/src/optimization/bun-utilities.ts +0 -778
- package/src/query-builder-integration.ts +0 -137
- package/src/request/context.ts +0 -62
- package/src/request/enhanced-request.ts +0 -857
- package/src/request/macros.ts +0 -688
- package/src/response/macros.ts +0 -612
- package/src/response/response-factory.ts +0 -596
- package/src/router/api-routes.ts +0 -243
- package/src/router/file-based-routing.ts +0 -670
- package/src/router/file-streaming.ts +0 -372
- package/src/router/fluent-router.ts +0 -927
- package/src/router/fluent-routing.ts +0 -797
- package/src/router/group-organization.ts +0 -213
- package/src/router/handler-resolver.ts +0 -291
- package/src/router/http-methods.ts +0 -377
- package/src/router/index.ts +0 -187
- package/src/router/middleware-groups.ts +0 -222
- package/src/router/middleware-integration.ts +0 -399
- package/src/router/middleware.ts +0 -231
- package/src/router/model-binding.ts +0 -215
- package/src/router/optimized-route-matching.ts +0 -253
- package/src/router/route-building.ts +0 -212
- package/src/router/route-compiler.ts +0 -702
- package/src/router/route-matching.ts +0 -349
- package/src/router/route-trie.ts +0 -464
- package/src/router/router.ts +0 -1581
- package/src/router/server.ts +0 -462
- package/src/router/validation-integration.ts +0 -443
- package/src/router/view-rendering.ts +0 -233
- package/src/router/websocket.ts +0 -100
- package/src/routing/route-caching.ts +0 -402
- package/src/routing/route-throttling.ts +0 -469
- package/src/routing/subdomain-routing.ts +0 -492
- package/src/session/database-store.ts +0 -109
- package/src/session/file-store.ts +0 -148
- package/src/session/index.ts +0 -244
- package/src/session/memory-store.ts +0 -88
- package/src/session/redis-store.ts +0 -93
- package/src/streaming/index.ts +0 -17
- package/src/streaming/sse-handler.ts +0 -482
- package/src/streaming/stream-handler.ts +0 -552
- package/src/testing/auth-testing.ts +0 -446
- package/src/testing/file-upload-testing.ts +0 -543
- package/src/testing/index.ts +0 -10
- package/src/testing/middleware-testing.ts +0 -322
- package/src/testing/model-binding-testing.ts +0 -645
- package/src/testing/performance-testing.ts +0 -738
- package/src/testing/test-client.ts +0 -310
- package/src/testing/test-request.ts +0 -315
- package/src/testing/test-response.ts +0 -332
- package/src/testing/types.ts +0 -224
- package/src/testing/websocket-testing.ts +0 -590
- package/src/types/controller-types.ts +0 -385
- package/src/types/core.ts +0 -683
- package/src/types/middleware-types.ts +0 -419
- package/src/types/request-response-augmentation.ts +0 -489
- package/src/types/route-inference.ts +0 -357
- package/src/types.ts +0 -2055
- package/src/url.ts +0 -126
- package/src/utils/index.ts +0 -1
- package/src/utils/query-preservation.ts +0 -201
- package/src/utils.ts +0 -327
- package/src/validation/validator.ts +0 -685
- package/src/websocket/clustering.ts +0 -762
|
@@ -1,449 +0,0 @@
|
|
|
1
|
-
import type { EnhancedRequest, NextFunction } from '../types'
|
|
2
|
-
import { config } from '../config'
|
|
3
|
-
|
|
4
|
-
export interface ValidationRule {
|
|
5
|
-
type: 'string' | 'number' | 'boolean' | 'email' | 'url' | 'uuid' | 'date' | 'regex' | 'custom'
|
|
6
|
-
required?: boolean
|
|
7
|
-
min?: number
|
|
8
|
-
max?: number
|
|
9
|
-
pattern?: RegExp
|
|
10
|
-
enum?: unknown[]
|
|
11
|
-
custom?: (value: unknown) => boolean | string
|
|
12
|
-
sanitize?: boolean
|
|
13
|
-
transform?: (value: unknown) => unknown
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ValidationSchema {
|
|
17
|
-
[key: string]: ValidationRule | ValidationSchema
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface InputValidationOptions {
|
|
21
|
-
enabled?: boolean
|
|
22
|
-
schemas?: {
|
|
23
|
-
query?: ValidationSchema
|
|
24
|
-
body?: ValidationSchema
|
|
25
|
-
headers?: ValidationSchema
|
|
26
|
-
params?: ValidationSchema
|
|
27
|
-
}
|
|
28
|
-
sanitizeByDefault?: boolean
|
|
29
|
-
strictMode?: boolean
|
|
30
|
-
allowUnknownFields?: boolean
|
|
31
|
-
maxDepth?: number
|
|
32
|
-
onValidationError?: (errors: ValidationError[]) => Response
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface ValidationError {
|
|
36
|
-
field: string
|
|
37
|
-
message: string
|
|
38
|
-
value: any
|
|
39
|
-
rule: string
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export default class InputValidation {
|
|
43
|
-
private options: InputValidationOptions
|
|
44
|
-
|
|
45
|
-
constructor(options: InputValidationOptions = {}) {
|
|
46
|
-
const validationConfig = config.server?.security?.inputValidation || {}
|
|
47
|
-
|
|
48
|
-
this.options = {
|
|
49
|
-
enabled: options.enabled ?? validationConfig.enabled ?? true,
|
|
50
|
-
schemas: (options.schemas ?? validationConfig.schemas ?? {}) as InputValidationOptions['schemas'],
|
|
51
|
-
sanitizeByDefault: options.sanitizeByDefault ?? validationConfig.sanitizeByDefault ?? true,
|
|
52
|
-
strictMode: options.strictMode ?? validationConfig.strictMode ?? false,
|
|
53
|
-
allowUnknownFields: options.allowUnknownFields ?? validationConfig.allowUnknownFields ?? true,
|
|
54
|
-
maxDepth: options.maxDepth ?? validationConfig.maxDepth ?? 10,
|
|
55
|
-
onValidationError: options.onValidationError,
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
private validateValue(value: any, rule: ValidationRule, fieldPath: string): ValidationError[] {
|
|
60
|
-
const errors: ValidationError[] = []
|
|
61
|
-
|
|
62
|
-
// Check required
|
|
63
|
-
if (rule.required && (value === undefined || value === null || value === '')) {
|
|
64
|
-
errors.push({
|
|
65
|
-
field: fieldPath,
|
|
66
|
-
message: 'Field is required',
|
|
67
|
-
value,
|
|
68
|
-
rule: 'required',
|
|
69
|
-
})
|
|
70
|
-
return errors
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Skip validation if value is undefined/null and not required
|
|
74
|
-
if (value === undefined || value === null) {
|
|
75
|
-
return errors
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Type validation
|
|
79
|
-
switch (rule.type) {
|
|
80
|
-
case 'string':
|
|
81
|
-
if (typeof value !== 'string') {
|
|
82
|
-
errors.push({
|
|
83
|
-
field: fieldPath,
|
|
84
|
-
message: 'Must be a string',
|
|
85
|
-
value,
|
|
86
|
-
rule: 'type',
|
|
87
|
-
})
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
if (rule.min !== undefined && value.length < rule.min) {
|
|
91
|
-
errors.push({
|
|
92
|
-
field: fieldPath,
|
|
93
|
-
message: `Must be at least ${rule.min} characters`,
|
|
94
|
-
value,
|
|
95
|
-
rule: 'min',
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
if (rule.max !== undefined && value.length > rule.max) {
|
|
99
|
-
errors.push({
|
|
100
|
-
field: fieldPath,
|
|
101
|
-
message: `Must be at most ${rule.max} characters`,
|
|
102
|
-
value,
|
|
103
|
-
rule: 'max',
|
|
104
|
-
})
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
break
|
|
108
|
-
|
|
109
|
-
case 'number': {
|
|
110
|
-
const num = Number(value)
|
|
111
|
-
if (Number.isNaN(num)) {
|
|
112
|
-
errors.push({
|
|
113
|
-
field: fieldPath,
|
|
114
|
-
message: 'Must be a number',
|
|
115
|
-
value,
|
|
116
|
-
rule: 'type',
|
|
117
|
-
})
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
if (rule.min !== undefined && num < rule.min) {
|
|
121
|
-
errors.push({
|
|
122
|
-
field: fieldPath,
|
|
123
|
-
message: `Must be at least ${rule.min}`,
|
|
124
|
-
value,
|
|
125
|
-
rule: 'min',
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
if (rule.max !== undefined && num > rule.max) {
|
|
129
|
-
errors.push({
|
|
130
|
-
field: fieldPath,
|
|
131
|
-
message: `Must be at most ${rule.max}`,
|
|
132
|
-
value,
|
|
133
|
-
rule: 'max',
|
|
134
|
-
})
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
break
|
|
138
|
-
}
|
|
139
|
-
case 'boolean':
|
|
140
|
-
if (typeof value !== 'boolean' && value !== 'true' && value !== 'false') {
|
|
141
|
-
errors.push({
|
|
142
|
-
field: fieldPath,
|
|
143
|
-
message: 'Must be a boolean',
|
|
144
|
-
value,
|
|
145
|
-
rule: 'type',
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
break
|
|
149
|
-
|
|
150
|
-
case 'email': {
|
|
151
|
-
const emailRegex = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/
|
|
152
|
-
if (typeof value === 'string' && !emailRegex.test(value)) {
|
|
153
|
-
errors.push({
|
|
154
|
-
field: fieldPath,
|
|
155
|
-
message: 'Must be a valid email address',
|
|
156
|
-
value,
|
|
157
|
-
rule: 'format',
|
|
158
|
-
})
|
|
159
|
-
}
|
|
160
|
-
break
|
|
161
|
-
}
|
|
162
|
-
case 'url':
|
|
163
|
-
try {
|
|
164
|
-
void new URL(value)
|
|
165
|
-
}
|
|
166
|
-
catch {
|
|
167
|
-
errors.push({
|
|
168
|
-
field: fieldPath,
|
|
169
|
-
message: 'Must be a valid URL',
|
|
170
|
-
value,
|
|
171
|
-
rule: 'format',
|
|
172
|
-
})
|
|
173
|
-
}
|
|
174
|
-
break
|
|
175
|
-
|
|
176
|
-
case 'uuid': {
|
|
177
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
178
|
-
if (typeof value === 'string' && !uuidRegex.test(value)) {
|
|
179
|
-
errors.push({
|
|
180
|
-
field: fieldPath,
|
|
181
|
-
message: 'Must be a valid UUID',
|
|
182
|
-
value,
|
|
183
|
-
rule: 'format',
|
|
184
|
-
})
|
|
185
|
-
}
|
|
186
|
-
break
|
|
187
|
-
}
|
|
188
|
-
case 'date': {
|
|
189
|
-
const date = new Date(value)
|
|
190
|
-
if (Number.isNaN(date.getTime())) {
|
|
191
|
-
errors.push({
|
|
192
|
-
field: fieldPath,
|
|
193
|
-
message: 'Must be a valid date',
|
|
194
|
-
value,
|
|
195
|
-
rule: 'format',
|
|
196
|
-
})
|
|
197
|
-
}
|
|
198
|
-
break
|
|
199
|
-
}
|
|
200
|
-
case 'regex':
|
|
201
|
-
if (rule.pattern && typeof value === 'string' && !rule.pattern.test(value)) {
|
|
202
|
-
errors.push({
|
|
203
|
-
field: fieldPath,
|
|
204
|
-
message: 'Does not match required pattern',
|
|
205
|
-
value,
|
|
206
|
-
rule: 'pattern',
|
|
207
|
-
})
|
|
208
|
-
}
|
|
209
|
-
break
|
|
210
|
-
|
|
211
|
-
case 'custom':
|
|
212
|
-
if (rule.custom) {
|
|
213
|
-
const result = rule.custom(value)
|
|
214
|
-
if (result !== true) {
|
|
215
|
-
errors.push({
|
|
216
|
-
field: fieldPath,
|
|
217
|
-
message: typeof result === 'string' ? result : 'Custom validation failed',
|
|
218
|
-
value,
|
|
219
|
-
rule: 'custom',
|
|
220
|
-
})
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
break
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Enum validation
|
|
227
|
-
if (rule.enum && !rule.enum.includes(value)) {
|
|
228
|
-
errors.push({
|
|
229
|
-
field: fieldPath,
|
|
230
|
-
message: `Must be one of: ${rule.enum.join(', ')}`,
|
|
231
|
-
value,
|
|
232
|
-
rule: 'enum',
|
|
233
|
-
})
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Pattern validation
|
|
237
|
-
if (rule.pattern && typeof value === 'string' && !rule.pattern.test(value)) {
|
|
238
|
-
errors.push({
|
|
239
|
-
field: fieldPath,
|
|
240
|
-
message: 'Does not match required pattern',
|
|
241
|
-
value,
|
|
242
|
-
rule: 'pattern',
|
|
243
|
-
})
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return errors
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
private sanitizeValue(value: any, rule: ValidationRule): any {
|
|
250
|
-
if (!rule.sanitize && !this.options.sanitizeByDefault) {
|
|
251
|
-
return value
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (typeof value === 'string') {
|
|
255
|
-
// Basic HTML sanitization
|
|
256
|
-
value = value
|
|
257
|
-
.replace(/[<>'"&]/g, (match) => {
|
|
258
|
-
const entities: Record<string, string> = {
|
|
259
|
-
'<': '<',
|
|
260
|
-
'>': '>',
|
|
261
|
-
'"': '"',
|
|
262
|
-
'\'': ''',
|
|
263
|
-
'&': '&',
|
|
264
|
-
}
|
|
265
|
-
return entities[match] || match
|
|
266
|
-
})
|
|
267
|
-
.trim()
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Apply transformation if provided
|
|
271
|
-
if (rule.transform) {
|
|
272
|
-
value = rule.transform(value)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return value
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
private validateObject(obj: any, schema: ValidationSchema, basePath = '', depth = 0): ValidationError[] {
|
|
279
|
-
const errors: ValidationError[] = []
|
|
280
|
-
|
|
281
|
-
if (depth > this.options.maxDepth!) {
|
|
282
|
-
errors.push({
|
|
283
|
-
field: basePath,
|
|
284
|
-
message: 'Maximum nesting depth exceeded',
|
|
285
|
-
value: obj,
|
|
286
|
-
rule: 'depth',
|
|
287
|
-
})
|
|
288
|
-
return errors
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Check for unknown fields in strict mode
|
|
292
|
-
if (this.options.strictMode && !this.options.allowUnknownFields) {
|
|
293
|
-
for (const key in obj) {
|
|
294
|
-
if (!(key in schema)) {
|
|
295
|
-
errors.push({
|
|
296
|
-
field: basePath ? `${basePath}.${key}` : key,
|
|
297
|
-
message: 'Unknown field',
|
|
298
|
-
value: obj[key],
|
|
299
|
-
rule: 'unknown',
|
|
300
|
-
})
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Validate known fields
|
|
306
|
-
for (const [key, rule] of Object.entries(schema)) {
|
|
307
|
-
const fieldPath = basePath ? `${basePath}.${key}` : key
|
|
308
|
-
const value = obj?.[key]
|
|
309
|
-
|
|
310
|
-
if (typeof rule === 'object' && !('type' in rule)) {
|
|
311
|
-
// Nested schema
|
|
312
|
-
if (value && typeof value === 'object') {
|
|
313
|
-
errors.push(...this.validateObject(value, rule as ValidationSchema, fieldPath, depth + 1))
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
else {
|
|
317
|
-
// Validation rule
|
|
318
|
-
const validationRule = rule as ValidationRule
|
|
319
|
-
errors.push(...this.validateValue(value, validationRule, fieldPath))
|
|
320
|
-
|
|
321
|
-
// Sanitize if needed
|
|
322
|
-
if (obj && (validationRule.sanitize || this.options.sanitizeByDefault)) {
|
|
323
|
-
obj[key] = this.sanitizeValue(value, validationRule)
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
return errors
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
private async parseRequestBody(req: EnhancedRequest): Promise<any> {
|
|
332
|
-
const contentType = req.headers.get('content-type') || ''
|
|
333
|
-
|
|
334
|
-
try {
|
|
335
|
-
if (contentType.includes('application/json')) {
|
|
336
|
-
return await req.json()
|
|
337
|
-
}
|
|
338
|
-
else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
339
|
-
const formData = await req.formData()
|
|
340
|
-
const result: Record<string, any> = {}
|
|
341
|
-
for (const [key, value] of (formData as any).entries()) {
|
|
342
|
-
result[key] = value
|
|
343
|
-
}
|
|
344
|
-
return result
|
|
345
|
-
}
|
|
346
|
-
else if (contentType.includes('multipart/form-data')) {
|
|
347
|
-
const formData = await req.formData()
|
|
348
|
-
const result: Record<string, any> = {}
|
|
349
|
-
for (const [key, value] of (formData as any).entries()) {
|
|
350
|
-
result[key] = value
|
|
351
|
-
}
|
|
352
|
-
return result
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
catch (error) {
|
|
356
|
-
console.error(error)
|
|
357
|
-
throw new Error('Invalid request body format')
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
return {}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
async handle(req: EnhancedRequest, next: NextFunction): Promise<Response> {
|
|
364
|
-
if (!this.options.enabled) {
|
|
365
|
-
const response = await next()
|
|
366
|
-
return response || new Response('Not Found', { status: 404 })
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const allErrors: ValidationError[] = []
|
|
370
|
-
const url = new URL(req.url)
|
|
371
|
-
|
|
372
|
-
try {
|
|
373
|
-
// Validate query parameters
|
|
374
|
-
if (this.options.schemas?.query) {
|
|
375
|
-
const queryParams: Record<string, any> = {}
|
|
376
|
-
for (const [key, value] of url.searchParams.entries()) {
|
|
377
|
-
queryParams[key] = value
|
|
378
|
-
}
|
|
379
|
-
const queryErrors = this.validateObject(queryParams, this.options.schemas.query)
|
|
380
|
-
allErrors.push(...queryErrors)
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Validate request body
|
|
384
|
-
if (this.options.schemas?.body && req.method !== 'GET' && req.method !== 'HEAD') {
|
|
385
|
-
try {
|
|
386
|
-
const body = await this.parseRequestBody(req)
|
|
387
|
-
const bodyErrors = this.validateObject(body, this.options.schemas.body)
|
|
388
|
-
allErrors.push(...bodyErrors)
|
|
389
|
-
|
|
390
|
-
// Attach validated body to request
|
|
391
|
-
req.validatedBody = body
|
|
392
|
-
}
|
|
393
|
-
catch (error) {
|
|
394
|
-
allErrors.push({
|
|
395
|
-
field: 'body',
|
|
396
|
-
message: error instanceof Error ? error.message : 'Invalid request body',
|
|
397
|
-
value: null,
|
|
398
|
-
rule: 'parse',
|
|
399
|
-
})
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Validate headers
|
|
404
|
-
if (this.options.schemas?.headers) {
|
|
405
|
-
const headers: Record<string, any> = {}
|
|
406
|
-
for (const [key, value] of req.headers.entries()) {
|
|
407
|
-
headers[key] = value
|
|
408
|
-
}
|
|
409
|
-
const headerErrors = this.validateObject(headers, this.options.schemas.headers)
|
|
410
|
-
allErrors.push(...headerErrors)
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Validate route parameters
|
|
414
|
-
if (this.options.schemas?.params && req.params) {
|
|
415
|
-
const paramErrors = this.validateObject(req.params, this.options.schemas.params)
|
|
416
|
-
allErrors.push(...paramErrors)
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Return validation errors if any
|
|
420
|
-
if (allErrors.length > 0) {
|
|
421
|
-
if (this.options.onValidationError) {
|
|
422
|
-
return this.options.onValidationError(allErrors)
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return new Response(JSON.stringify({
|
|
426
|
-
error: 'Validation Error',
|
|
427
|
-
message: 'Request validation failed',
|
|
428
|
-
errors: allErrors,
|
|
429
|
-
}), {
|
|
430
|
-
status: 400,
|
|
431
|
-
headers: { 'Content-Type': 'application/json' },
|
|
432
|
-
})
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
const response = await next()
|
|
436
|
-
return response || new Response('Not Found', { status: 404 })
|
|
437
|
-
}
|
|
438
|
-
catch (error) {
|
|
439
|
-
console.error('Input validation error:', error)
|
|
440
|
-
return new Response('Internal Server Error', { status: 500 })
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// Factory function for easy use
|
|
446
|
-
export function inputValidation(options: InputValidationOptions = {}): (req: EnhancedRequest, next: NextFunction) => Promise<Response> {
|
|
447
|
-
const instance = new InputValidation(options)
|
|
448
|
-
return async (req: EnhancedRequest, next: NextFunction) => instance.handle(req, next)
|
|
449
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { EnhancedRequest, NextFunction } from '../types'
|
|
2
|
-
|
|
3
|
-
export default class JsonBody {
|
|
4
|
-
async handle(req: EnhancedRequest, next: NextFunction): Promise<Response> {
|
|
5
|
-
// Only process requests with application/json content type
|
|
6
|
-
const contentType = req.headers.get('content-type')
|
|
7
|
-
if (contentType && contentType.includes('application/json')) {
|
|
8
|
-
try {
|
|
9
|
-
// Clone the request since it can only be used once
|
|
10
|
-
const clonedReq = req.clone()
|
|
11
|
-
const body = await clonedReq.json()
|
|
12
|
-
|
|
13
|
-
// Attach the parsed body to the request object
|
|
14
|
-
Object.defineProperty(req, 'jsonBody', {
|
|
15
|
-
value: body,
|
|
16
|
-
writable: true,
|
|
17
|
-
enumerable: true,
|
|
18
|
-
configurable: true,
|
|
19
|
-
})
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
// If JSON parsing fails, return a 400 Bad Request
|
|
23
|
-
return new Response(
|
|
24
|
-
JSON.stringify({ error: 'Invalid JSON payload' }),
|
|
25
|
-
{
|
|
26
|
-
status: 400,
|
|
27
|
-
headers: { 'Content-Type': 'application/json' },
|
|
28
|
-
},
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Continue to next middleware
|
|
34
|
-
const response = await next()
|
|
35
|
-
return response || new Response('Not Found', { status: 404 })
|
|
36
|
-
}
|
|
37
|
-
}
|