@getvision/adapter-fastify 0.0.10 → 0.1.0-6e5c887-develop
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 +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -8
- package/dist/validator.d.ts +69 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +68 -0
- package/package.json +2 -2
- package/src/index.ts +38 -12
- package/src/validator.ts +177 -0
package/CHANGELOG.md
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -15,4 +15,5 @@ export declare function enableAutoDiscovery(fastify: FastifyInstance, options?:
|
|
|
15
15
|
services?: ServiceDefinition[];
|
|
16
16
|
}): void;
|
|
17
17
|
export { generateZodTemplate } from '@getvision/core';
|
|
18
|
+
export { validator, toFastifySchema } from './validator';
|
|
18
19
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAElE,OAAO,EACL,UAAU,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAElE,OAAO,EACL,UAAU,EASX,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAIV,KAAK,EACL,oBAAoB,EACpB,iBAAiB,EAClB,MAAM,iBAAiB,CAAA;AAGxB,UAAU,aAAa;IACrB,MAAM,EAAE,UAAU,CAAA;IAClB,KAAK,EAAE,KAAK,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,gBAAgB,IAAI,aAAa,CAMhD;AAED,wBAAgB,aAAa,KAInB,CAAC,EACP,MAAM,MAAM,EACZ,YAAY,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,YAAK,EACpC,IAAI,MAAM,CAAC,KACV,CAAC,CAgCL;AAKD,wBAAgB,iBAAiB,IAAI,UAAU,GAAG,IAAI,CAErD;AAyQD,eAAO,MAAM,YAAY,0CAGvB,CAAA;AAEF,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,eAAe,EACxB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,iBAAiB,EAAE,CAAA;CAAE,GAC3C,IAAI,CAsEN;AA+FD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAErD,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fp from 'fastify-plugin';
|
|
2
|
-
import { VisionCore, autoDetectPackageInfo, autoDetectIntegrations, detectDrizzle, startDrizzleStudio, stopDrizzleStudio, traceContext, } from '@getvision/core';
|
|
2
|
+
import { VisionCore, autoDetectPackageInfo, autoDetectIntegrations, detectDrizzle, startDrizzleStudio, stopDrizzleStudio, generateTemplate, traceContext, } from '@getvision/core';
|
|
3
3
|
import { fastifyRequestContext, requestContext } from '@fastify/request-context';
|
|
4
4
|
export function getVisionContext() {
|
|
5
5
|
const ctx = requestContext.get('visionTrace');
|
|
@@ -124,17 +124,35 @@ const visionPluginImpl = async (fastify, options) => {
|
|
|
124
124
|
});
|
|
125
125
|
});
|
|
126
126
|
const CAPTURE_KEY = Symbol.for('vision.fastify.routes');
|
|
127
|
-
const captured = (fastify[CAPTURE_KEY] = fastify[CAPTURE_KEY] || []);
|
|
128
127
|
fastify.addHook('onRoute', (routeOpts) => {
|
|
128
|
+
if (!fastify[CAPTURE_KEY]) {
|
|
129
|
+
fastify[CAPTURE_KEY] = [];
|
|
130
|
+
}
|
|
131
|
+
const captured = fastify[CAPTURE_KEY];
|
|
129
132
|
const methods = Array.isArray(routeOpts.method) ? routeOpts.method : [routeOpts.method];
|
|
133
|
+
// Extract schema from preHandler validator if present
|
|
134
|
+
let visionSchema = undefined;
|
|
135
|
+
if (routeOpts.preHandler) {
|
|
136
|
+
const handlers = Array.isArray(routeOpts.preHandler) ? routeOpts.preHandler : [routeOpts.preHandler];
|
|
137
|
+
for (const handler of handlers) {
|
|
138
|
+
if (handler.__visionSchema) {
|
|
139
|
+
visionSchema = handler.__visionSchema;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
130
144
|
for (const m of methods) {
|
|
131
145
|
const method = (m || '').toString().toUpperCase();
|
|
132
146
|
if (!method || method === 'HEAD' || method === 'OPTIONS')
|
|
133
147
|
continue;
|
|
148
|
+
const schema = routeOpts.schema ? { ...routeOpts.schema } : {};
|
|
149
|
+
if (visionSchema) {
|
|
150
|
+
schema.__visionSchema = visionSchema;
|
|
151
|
+
}
|
|
134
152
|
captured.push({
|
|
135
153
|
method,
|
|
136
154
|
url: routeOpts.url,
|
|
137
|
-
schema
|
|
155
|
+
schema,
|
|
138
156
|
handlerName: routeOpts.handler?.name || 'anonymous',
|
|
139
157
|
});
|
|
140
158
|
}
|
|
@@ -264,7 +282,15 @@ export function enableAutoDiscovery(fastify, options) {
|
|
|
264
282
|
handler: route.handlerName || 'anonymous',
|
|
265
283
|
};
|
|
266
284
|
// Try to get schema from route
|
|
267
|
-
if (route.schema?.
|
|
285
|
+
if (route.schema?.__visionSchema) {
|
|
286
|
+
try {
|
|
287
|
+
routeMeta.requestBody = generateTemplate(route.schema.__visionSchema);
|
|
288
|
+
}
|
|
289
|
+
catch (e) {
|
|
290
|
+
console.error(`[Vision] Template generation error for ${route.method} ${route.url}:`, e);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
else if (route.schema?.body) {
|
|
268
294
|
try {
|
|
269
295
|
routeMeta.requestBody = jsonSchemaToTemplate(route.schema.body);
|
|
270
296
|
}
|
|
@@ -340,11 +366,8 @@ function jsonSchemaToTemplate(schema) {
|
|
|
340
366
|
default:
|
|
341
367
|
value = 'null';
|
|
342
368
|
}
|
|
343
|
-
const comment = description
|
|
344
|
-
? ` // ${description}${isRequired ? '' : ' (optional)'}`
|
|
345
|
-
: (isRequired ? '' : ' // optional');
|
|
346
369
|
const comma = index < props.length - 1 ? ',' : '';
|
|
347
|
-
lines.push(` "${key}": ${value}${comma}
|
|
370
|
+
lines.push(` "${key}": ${value}${comma}`);
|
|
348
371
|
fields.push({
|
|
349
372
|
name: key,
|
|
350
373
|
type,
|
|
@@ -388,3 +411,4 @@ function matchPattern(path, pattern) {
|
|
|
388
411
|
return path === pattern;
|
|
389
412
|
}
|
|
390
413
|
export { generateZodTemplate } from '@getvision/core';
|
|
414
|
+
export { validator, toFastifySchema } from './validator';
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { FastifySchema, FastifyRequest, FastifyReply } from 'fastify';
|
|
2
|
+
import type { StandardSchemaV1, ValidationSchema } from '@getvision/core';
|
|
3
|
+
/**
|
|
4
|
+
* Convert a Standard Schema to Fastify schema format
|
|
5
|
+
* This allows using any validation library with Fastify's built-in validation
|
|
6
|
+
*/
|
|
7
|
+
export declare function toFastifySchema(schema: {
|
|
8
|
+
body?: ValidationSchema;
|
|
9
|
+
querystring?: ValidationSchema;
|
|
10
|
+
params?: ValidationSchema;
|
|
11
|
+
headers?: ValidationSchema;
|
|
12
|
+
response?: {
|
|
13
|
+
[statusCode: number]: ValidationSchema;
|
|
14
|
+
};
|
|
15
|
+
}): FastifySchema;
|
|
16
|
+
/**
|
|
17
|
+
* Universal validator for Fastify routes
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { validator } from '@getvision/adapter-fastify'
|
|
22
|
+
* import { z } from 'zod'
|
|
23
|
+
*
|
|
24
|
+
* const userSchema = z.object({
|
|
25
|
+
* name: z.string(),
|
|
26
|
+
* email: z.string().email()
|
|
27
|
+
* })
|
|
28
|
+
*
|
|
29
|
+
* fastify.post('/users', {
|
|
30
|
+
* preHandler: validator('body', userSchema)
|
|
31
|
+
* }, async (request, reply) => {
|
|
32
|
+
* // request.body is now validated
|
|
33
|
+
* return { success: true }
|
|
34
|
+
* })
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function validator<S extends import('zod').ZodTypeAny>(target: 'body', schema: S): (request: FastifyRequest & {
|
|
38
|
+
body: import('zod').infer<S>;
|
|
39
|
+
}, reply: FastifyReply) => Promise<void>;
|
|
40
|
+
export declare function validator<S extends import('zod').ZodTypeAny>(target: 'querystring', schema: S): (request: FastifyRequest & {
|
|
41
|
+
query: import('zod').infer<S>;
|
|
42
|
+
}, reply: FastifyReply) => Promise<void>;
|
|
43
|
+
export declare function validator<S extends import('zod').ZodTypeAny>(target: 'params', schema: S): (request: FastifyRequest & {
|
|
44
|
+
params: import('zod').infer<S>;
|
|
45
|
+
}, reply: FastifyReply) => Promise<void>;
|
|
46
|
+
export declare function validator<S extends import('zod').ZodTypeAny>(target: 'headers', schema: S): (request: FastifyRequest & {
|
|
47
|
+
headers: import('zod').infer<S>;
|
|
48
|
+
}, reply: FastifyReply) => Promise<void>;
|
|
49
|
+
export declare function validator<S extends StandardSchemaV1<any, any>>(target: 'body', schema: S): (request: FastifyRequest & {
|
|
50
|
+
body: StandardSchemaV1.Infer<S>['output'];
|
|
51
|
+
}, reply: FastifyReply) => Promise<void>;
|
|
52
|
+
export declare function validator<S extends StandardSchemaV1<any, any>>(target: 'querystring', schema: S): (request: FastifyRequest & {
|
|
53
|
+
query: StandardSchemaV1.Infer<S>['output'];
|
|
54
|
+
}, reply: FastifyReply) => Promise<void>;
|
|
55
|
+
export declare function validator<S extends StandardSchemaV1<any, any>>(target: 'params', schema: S): (request: FastifyRequest & {
|
|
56
|
+
params: StandardSchemaV1.Infer<S>['output'];
|
|
57
|
+
}, reply: FastifyReply) => Promise<void>;
|
|
58
|
+
export declare function validator<S extends StandardSchemaV1<any, any>>(target: 'headers', schema: S): (request: FastifyRequest & {
|
|
59
|
+
headers: StandardSchemaV1.Infer<S>['output'];
|
|
60
|
+
}, reply: FastifyReply) => Promise<void>;
|
|
61
|
+
export declare function validator(target: 'body', schema: ValidationSchema): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
62
|
+
export declare function validator(target: 'querystring', schema: ValidationSchema): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
63
|
+
export declare function validator(target: 'params', schema: ValidationSchema): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
64
|
+
export declare function validator(target: 'headers', schema: ValidationSchema): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Extract schema from route configuration
|
|
67
|
+
*/
|
|
68
|
+
export declare function extractSchema(routeOptions: any): ValidationSchema | undefined;
|
|
69
|
+
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ValidationError, createValidationErrorResponse, UniversalValidator } from '@getvision/core';
|
|
2
|
+
/**
|
|
3
|
+
* Convert a Standard Schema to Fastify schema format
|
|
4
|
+
* This allows using any validation library with Fastify's built-in validation
|
|
5
|
+
*/
|
|
6
|
+
export function toFastifySchema(schema) {
|
|
7
|
+
const fastifySchema = {};
|
|
8
|
+
// Note: Fastify has its own validation system
|
|
9
|
+
// This is a bridge to allow Standard Schema libraries to work with Fastify
|
|
10
|
+
// In practice, you might want to use Fastify's native validation or override it
|
|
11
|
+
return fastifySchema;
|
|
12
|
+
}
|
|
13
|
+
export function validator(target, schema) {
|
|
14
|
+
const handler = async (request, reply) => {
|
|
15
|
+
let data;
|
|
16
|
+
// Extract data based on target
|
|
17
|
+
switch (target) {
|
|
18
|
+
case 'body':
|
|
19
|
+
data = request.body;
|
|
20
|
+
break;
|
|
21
|
+
case 'querystring':
|
|
22
|
+
data = request.query;
|
|
23
|
+
break;
|
|
24
|
+
case 'params':
|
|
25
|
+
data = request.params;
|
|
26
|
+
break;
|
|
27
|
+
case 'headers':
|
|
28
|
+
data = request.headers;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
// Validate data using UniversalValidator
|
|
33
|
+
const validated = UniversalValidator.parse(schema, data);
|
|
34
|
+
// Store validated data back
|
|
35
|
+
switch (target) {
|
|
36
|
+
case 'body':
|
|
37
|
+
request.body = validated;
|
|
38
|
+
break;
|
|
39
|
+
case 'querystring':
|
|
40
|
+
request.query = validated;
|
|
41
|
+
break;
|
|
42
|
+
case 'params':
|
|
43
|
+
request.params = validated;
|
|
44
|
+
break;
|
|
45
|
+
case 'headers':
|
|
46
|
+
// Fastify headers are read-only, but we can extend the request object
|
|
47
|
+
Object.assign(request.headers, validated);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof ValidationError) {
|
|
53
|
+
const requestId = request.headers['x-request-id'];
|
|
54
|
+
return reply.status(400).send(createValidationErrorResponse(error.issues, requestId));
|
|
55
|
+
}
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
handler.__visionSchema = schema;
|
|
60
|
+
handler.__visionTarget = target;
|
|
61
|
+
return handler;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Extract schema from route configuration
|
|
65
|
+
*/
|
|
66
|
+
export function extractSchema(routeOptions) {
|
|
67
|
+
return routeOptions?.schema?.__visionSchema;
|
|
68
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getvision/adapter-fastify",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0-6e5c887-develop",
|
|
4
4
|
"description": "Fastify adapter for Vision Dashboard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@fastify/request-context": "^6.2.1",
|
|
28
28
|
"fastify-plugin": "^5.1.0",
|
|
29
|
-
"@getvision/core": "0.0
|
|
29
|
+
"@getvision/core": "0.1.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"fastify": "^5.6.1",
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
detectDrizzle,
|
|
8
8
|
startDrizzleStudio,
|
|
9
9
|
stopDrizzleStudio,
|
|
10
|
+
generateTemplate,
|
|
11
|
+
runInTraceContext,
|
|
10
12
|
traceContext,
|
|
11
13
|
} from '@getvision/core'
|
|
12
14
|
import type {
|
|
@@ -189,18 +191,38 @@ const visionPluginImpl: FastifyPluginAsync<VisionFastifyOptions> = async (fastif
|
|
|
189
191
|
})
|
|
190
192
|
|
|
191
193
|
const CAPTURE_KEY = Symbol.for('vision.fastify.routes')
|
|
192
|
-
|
|
193
|
-
((fastify as any)[CAPTURE_KEY]
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
fastify.addHook('onRoute', (routeOpts) => {
|
|
195
|
+
if (!(fastify as any)[CAPTURE_KEY]) {
|
|
196
|
+
(fastify as any)[CAPTURE_KEY] = []
|
|
197
|
+
}
|
|
198
|
+
const captured = (fastify as any)[CAPTURE_KEY]
|
|
196
199
|
const methods = Array.isArray(routeOpts.method) ? routeOpts.method : [routeOpts.method]
|
|
200
|
+
|
|
201
|
+
// Extract schema from preHandler validator if present
|
|
202
|
+
let visionSchema: any = undefined
|
|
203
|
+
if (routeOpts.preHandler) {
|
|
204
|
+
const handlers = Array.isArray(routeOpts.preHandler) ? routeOpts.preHandler : [routeOpts.preHandler]
|
|
205
|
+
for (const handler of handlers) {
|
|
206
|
+
if ((handler as any).__visionSchema) {
|
|
207
|
+
visionSchema = (handler as any).__visionSchema
|
|
208
|
+
break
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
197
213
|
for (const m of methods) {
|
|
198
214
|
const method = (m || '').toString().toUpperCase()
|
|
199
215
|
if (!method || method === 'HEAD' || method === 'OPTIONS') continue
|
|
216
|
+
|
|
217
|
+
const schema: any = routeOpts.schema ? { ...routeOpts.schema } : {}
|
|
218
|
+
if (visionSchema) {
|
|
219
|
+
schema.__visionSchema = visionSchema
|
|
220
|
+
}
|
|
221
|
+
|
|
200
222
|
captured.push({
|
|
201
223
|
method,
|
|
202
224
|
url: routeOpts.url as string,
|
|
203
|
-
schema
|
|
225
|
+
schema,
|
|
204
226
|
handlerName: routeOpts.handler?.name || 'anonymous',
|
|
205
227
|
})
|
|
206
228
|
}
|
|
@@ -357,7 +379,13 @@ export function enableAutoDiscovery(
|
|
|
357
379
|
}
|
|
358
380
|
|
|
359
381
|
// Try to get schema from route
|
|
360
|
-
if (route.schema?.
|
|
382
|
+
if (route.schema?.__visionSchema) {
|
|
383
|
+
try {
|
|
384
|
+
routeMeta.requestBody = generateTemplate(route.schema.__visionSchema)
|
|
385
|
+
} catch (e) {
|
|
386
|
+
console.error(`[Vision] Template generation error for ${route.method} ${route.url}:`, e)
|
|
387
|
+
}
|
|
388
|
+
} else if (route.schema?.body) {
|
|
361
389
|
try {
|
|
362
390
|
routeMeta.requestBody = jsonSchemaToTemplate(route.schema.body)
|
|
363
391
|
} catch (e) {
|
|
@@ -442,12 +470,8 @@ function jsonSchemaToTemplate(schema: any): RequestBodySchema {
|
|
|
442
470
|
value = 'null'
|
|
443
471
|
}
|
|
444
472
|
|
|
445
|
-
const comment = description
|
|
446
|
-
? ` // ${description}${isRequired ? '' : ' (optional)'}`
|
|
447
|
-
: (isRequired ? '' : ' // optional')
|
|
448
|
-
|
|
449
473
|
const comma = index < props.length - 1 ? ',' : ''
|
|
450
|
-
lines.push(` "${key}": ${value}${comma}
|
|
474
|
+
lines.push(` "${key}": ${value}${comma}`)
|
|
451
475
|
|
|
452
476
|
fields.push({
|
|
453
477
|
name: key,
|
|
@@ -497,4 +521,6 @@ function matchPattern(path: string, pattern: string): boolean {
|
|
|
497
521
|
return path === pattern
|
|
498
522
|
}
|
|
499
523
|
|
|
500
|
-
export { generateZodTemplate } from '@getvision/core'
|
|
524
|
+
export { generateZodTemplate } from '@getvision/core'
|
|
525
|
+
|
|
526
|
+
export { validator, toFastifySchema } from './validator'
|
package/src/validator.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { FastifySchema, FastifyRequest, FastifyReply } from 'fastify'
|
|
2
|
+
import type { StandardSchemaV1, ValidationSchema } from '@getvision/core'
|
|
3
|
+
import {
|
|
4
|
+
ValidationError,
|
|
5
|
+
createValidationErrorResponse,
|
|
6
|
+
UniversalValidator
|
|
7
|
+
} from '@getvision/core'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Convert a Standard Schema to Fastify schema format
|
|
11
|
+
* This allows using any validation library with Fastify's built-in validation
|
|
12
|
+
*/
|
|
13
|
+
export function toFastifySchema(schema: {
|
|
14
|
+
body?: ValidationSchema
|
|
15
|
+
querystring?: ValidationSchema
|
|
16
|
+
params?: ValidationSchema
|
|
17
|
+
headers?: ValidationSchema
|
|
18
|
+
response?: {
|
|
19
|
+
[statusCode: number]: ValidationSchema
|
|
20
|
+
}
|
|
21
|
+
}): FastifySchema {
|
|
22
|
+
const fastifySchema: FastifySchema = {}
|
|
23
|
+
|
|
24
|
+
// Note: Fastify has its own validation system
|
|
25
|
+
// This is a bridge to allow Standard Schema libraries to work with Fastify
|
|
26
|
+
// In practice, you might want to use Fastify's native validation or override it
|
|
27
|
+
|
|
28
|
+
return fastifySchema
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Universal validator for Fastify routes
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { validator } from '@getvision/adapter-fastify'
|
|
37
|
+
* import { z } from 'zod'
|
|
38
|
+
*
|
|
39
|
+
* const userSchema = z.object({
|
|
40
|
+
* name: z.string(),
|
|
41
|
+
* email: z.string().email()
|
|
42
|
+
* })
|
|
43
|
+
*
|
|
44
|
+
* fastify.post('/users', {
|
|
45
|
+
* preHandler: validator('body', userSchema)
|
|
46
|
+
* }, async (request, reply) => {
|
|
47
|
+
* // request.body is now validated
|
|
48
|
+
* return { success: true }
|
|
49
|
+
* })
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function validator<S extends import('zod').ZodTypeAny>(
|
|
53
|
+
target: 'body',
|
|
54
|
+
schema: S
|
|
55
|
+
): (request: FastifyRequest & { body: import('zod').infer<S> }, reply: FastifyReply) => Promise<void>
|
|
56
|
+
|
|
57
|
+
export function validator<S extends import('zod').ZodTypeAny>(
|
|
58
|
+
target: 'querystring',
|
|
59
|
+
schema: S
|
|
60
|
+
): (request: FastifyRequest & { query: import('zod').infer<S> }, reply: FastifyReply) => Promise<void>
|
|
61
|
+
|
|
62
|
+
export function validator<S extends import('zod').ZodTypeAny>(
|
|
63
|
+
target: 'params',
|
|
64
|
+
schema: S
|
|
65
|
+
): (request: FastifyRequest & { params: import('zod').infer<S> }, reply: FastifyReply) => Promise<void>
|
|
66
|
+
|
|
67
|
+
export function validator<S extends import('zod').ZodTypeAny>(
|
|
68
|
+
target: 'headers',
|
|
69
|
+
schema: S
|
|
70
|
+
): (request: FastifyRequest & { headers: import('zod').infer<S> }, reply: FastifyReply) => Promise<void>
|
|
71
|
+
|
|
72
|
+
export function validator<S extends StandardSchemaV1<any, any>>(
|
|
73
|
+
target: 'body',
|
|
74
|
+
schema: S
|
|
75
|
+
): (request: FastifyRequest & { body: StandardSchemaV1.Infer<S>['output'] }, reply: FastifyReply) => Promise<void>
|
|
76
|
+
|
|
77
|
+
export function validator<S extends StandardSchemaV1<any, any>>(
|
|
78
|
+
target: 'querystring',
|
|
79
|
+
schema: S
|
|
80
|
+
): (request: FastifyRequest & { query: StandardSchemaV1.Infer<S>['output'] }, reply: FastifyReply) => Promise<void>
|
|
81
|
+
|
|
82
|
+
export function validator<S extends StandardSchemaV1<any, any>>(
|
|
83
|
+
target: 'params',
|
|
84
|
+
schema: S
|
|
85
|
+
): (request: FastifyRequest & { params: StandardSchemaV1.Infer<S>['output'] }, reply: FastifyReply) => Promise<void>
|
|
86
|
+
|
|
87
|
+
export function validator<S extends StandardSchemaV1<any, any>>(
|
|
88
|
+
target: 'headers',
|
|
89
|
+
schema: S
|
|
90
|
+
): (request: FastifyRequest & { headers: StandardSchemaV1.Infer<S>['output'] }, reply: FastifyReply) => Promise<void>
|
|
91
|
+
|
|
92
|
+
export function validator(
|
|
93
|
+
target: 'body',
|
|
94
|
+
schema: ValidationSchema
|
|
95
|
+
): (request: FastifyRequest, reply: FastifyReply) => Promise<void>
|
|
96
|
+
|
|
97
|
+
export function validator(
|
|
98
|
+
target: 'querystring',
|
|
99
|
+
schema: ValidationSchema
|
|
100
|
+
): (request: FastifyRequest, reply: FastifyReply) => Promise<void>
|
|
101
|
+
|
|
102
|
+
export function validator(
|
|
103
|
+
target: 'params',
|
|
104
|
+
schema: ValidationSchema
|
|
105
|
+
): (request: FastifyRequest, reply: FastifyReply) => Promise<void>
|
|
106
|
+
|
|
107
|
+
export function validator(
|
|
108
|
+
target: 'headers',
|
|
109
|
+
schema: ValidationSchema
|
|
110
|
+
): (request: FastifyRequest, reply: FastifyReply) => Promise<void>
|
|
111
|
+
|
|
112
|
+
export function validator(
|
|
113
|
+
target: 'body' | 'querystring' | 'params' | 'headers',
|
|
114
|
+
schema: ValidationSchema
|
|
115
|
+
) {
|
|
116
|
+
const handler = async (request: FastifyRequest, reply: FastifyReply) => {
|
|
117
|
+
let data: unknown
|
|
118
|
+
|
|
119
|
+
// Extract data based on target
|
|
120
|
+
switch (target) {
|
|
121
|
+
case 'body':
|
|
122
|
+
data = request.body
|
|
123
|
+
break
|
|
124
|
+
case 'querystring':
|
|
125
|
+
data = request.query
|
|
126
|
+
break
|
|
127
|
+
case 'params':
|
|
128
|
+
data = request.params
|
|
129
|
+
break
|
|
130
|
+
case 'headers':
|
|
131
|
+
data = request.headers
|
|
132
|
+
break
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
// Validate data using UniversalValidator
|
|
137
|
+
const validated = UniversalValidator.parse(schema, data)
|
|
138
|
+
|
|
139
|
+
// Store validated data back
|
|
140
|
+
switch (target) {
|
|
141
|
+
case 'body':
|
|
142
|
+
request.body = validated
|
|
143
|
+
break
|
|
144
|
+
case 'querystring':
|
|
145
|
+
request.query = validated as any
|
|
146
|
+
break
|
|
147
|
+
case 'params':
|
|
148
|
+
request.params = validated as any
|
|
149
|
+
break
|
|
150
|
+
case 'headers':
|
|
151
|
+
// Fastify headers are read-only, but we can extend the request object
|
|
152
|
+
Object.assign(request.headers, validated)
|
|
153
|
+
break
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (error instanceof ValidationError) {
|
|
157
|
+
const requestId = request.headers['x-request-id'] as string
|
|
158
|
+
return reply.status(400).send(
|
|
159
|
+
createValidationErrorResponse(error.issues, requestId)
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
throw error
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
(handler as any).__visionSchema = schema;
|
|
167
|
+
(handler as any).__visionTarget = target;
|
|
168
|
+
|
|
169
|
+
return handler
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Extract schema from route configuration
|
|
174
|
+
*/
|
|
175
|
+
export function extractSchema(routeOptions: any): ValidationSchema | undefined {
|
|
176
|
+
return routeOptions?.schema?.__visionSchema
|
|
177
|
+
}
|