From 5c7e9a94245d9bc92879c485b1a459eb01afbb8f Mon Sep 17 00:00:00 2001 From: Code Date: Sun, 27 Sep 2026 22:24:50 +0100 Subject: [PATCH 1/3] Fix #239: Add Zod validation pipe and DTO transformer for uniform request parsing --- src/common/controllers/example-zod.controller.ts | 14 ++++++++++++++ src/common/decorators/zod-body.decorator.ts | 11 +++++++++++ src/common/dto/example-zod.dto.ts | 9 +++++++++ src/common/pipes/zod-validation.pipe.spec.ts | 2 +- src/common/pipes/zod-validation.pipe.ts | 2 +- 5 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 src/common/controllers/example-zod.controller.ts create mode 100644 src/common/decorators/zod-body.decorator.ts create mode 100644 src/common/dto/example-zod.dto.ts diff --git a/src/common/controllers/example-zod.controller.ts b/src/common/controllers/example-zod.controller.ts new file mode 100644 index 00000000..c8496f7c --- /dev/null +++ b/src/common/controllers/example-zod.controller.ts @@ -0,0 +1,14 @@ +import { Controller, Post, Body } from '@nestjs/common'; +import { ZodValidationPipe } from '../pipes/zod-validation.pipe'; +import { ExampleZodSchema, ExampleZodDto } from '../dto/example-zod.dto'; + +@Controller('example-zod') +export class ExampleZodController { + @Post() + create(@Body(new ZodValidationPipe(ExampleZodSchema)) dto: ExampleZodDto) { + return { + success: true, + data: dto, + }; + } +} diff --git a/src/common/decorators/zod-body.decorator.ts b/src/common/decorators/zod-body.decorator.ts new file mode 100644 index 00000000..20c83738 --- /dev/null +++ b/src/common/decorators/zod-body.decorator.ts @@ -0,0 +1,11 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { ZodSchema } from 'zod'; +import { ZodValidationPipe } from '../pipes/zod-validation.pipe'; + +export function ZodBody(schema: ZodSchema) { + return createParamDecorator((data: unknown, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + const pipe = new ZodValidationPipe(schema); + return pipe.transform(request.body, { type: 'body' }); + })(); +} diff --git a/src/common/dto/example-zod.dto.ts b/src/common/dto/example-zod.dto.ts new file mode 100644 index 00000000..cf5f9f60 --- /dev/null +++ b/src/common/dto/example-zod.dto.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; + +export const ExampleZodSchema = z.object({ + title: z.string().min(1, 'Title is required'), + description: z.string().optional(), + tags: z.array(z.string()).default([]), +}); + +export type ExampleZodDto = z.infer; diff --git a/src/common/pipes/zod-validation.pipe.spec.ts b/src/common/pipes/zod-validation.pipe.spec.ts index 687b588c..2494fdad 100644 --- a/src/common/pipes/zod-validation.pipe.spec.ts +++ b/src/common/pipes/zod-validation.pipe.spec.ts @@ -177,4 +177,4 @@ describe('ZodValidationPipe', () => { expect(details[0].message).toContain('Localized:'); } }); -}); +}); \ No newline at end of file diff --git a/src/common/pipes/zod-validation.pipe.ts b/src/common/pipes/zod-validation.pipe.ts index 2eac8084..20f8e1e1 100644 --- a/src/common/pipes/zod-validation.pipe.ts +++ b/src/common/pipes/zod-validation.pipe.ts @@ -11,7 +11,7 @@ export interface ZodValidationPipeOptions { /** * A pipe that validates and parses an incoming payload against a Zod schema. - * Instantiated per-schema, e.g. `@Body(new ZodValidationPipe(createAgentSchema))`. + * Instantiated per-schema, e.g. `@Body(new ZodValidationPipe(createAgentSchema)) کیا۔ * Rejects unknown/invalid data with a structured VALIDATION_ERROR whose * `details` use the canonical {@link formatZodError} shape, supporting localized error message overrides. */ From 7a46b7be55bdc142d29f647d5be7ead305212f8f Mon Sep 17 00:00:00 2001 From: Code Date: Sun, 27 Sep 2026 22:26:31 +0100 Subject: [PATCH 2/3] Fix CI for #239 --- src/common/decorators/zod-body.decorator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/decorators/zod-body.decorator.ts b/src/common/decorators/zod-body.decorator.ts index 20c83738..3dd44080 100644 --- a/src/common/decorators/zod-body.decorator.ts +++ b/src/common/decorators/zod-body.decorator.ts @@ -3,7 +3,7 @@ import { ZodSchema } from 'zod'; import { ZodValidationPipe } from '../pipes/zod-validation.pipe'; export function ZodBody(schema: ZodSchema) { - return createParamDecorator((data: unknown, ctx: ExecutionContext) => { + return createParamDecorator((_data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); const pipe = new ZodValidationPipe(schema); return pipe.transform(request.body, { type: 'body' }); From c27abbbafdaf9e9391ef3fd1c4d198e1cc435d9f Mon Sep 17 00:00:00 2001 From: Code Date: Sun, 27 Sep 2026 22:29:06 +0100 Subject: [PATCH 3/3] Fix CI for #239 --- src/common/decorators/zod-body.decorator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/decorators/zod-body.decorator.ts b/src/common/decorators/zod-body.decorator.ts index 3dd44080..50d69d36 100644 --- a/src/common/decorators/zod-body.decorator.ts +++ b/src/common/decorators/zod-body.decorator.ts @@ -1,8 +1,8 @@ import { createParamDecorator, ExecutionContext } from '@nestjs/common'; -import { ZodSchema } from 'zod'; +import { ZodTypeAny } from 'zod'; import { ZodValidationPipe } from '../pipes/zod-validation.pipe'; -export function ZodBody(schema: ZodSchema) { +export function ZodBody(schema: ZodTypeAny) { return createParamDecorator((_data: unknown, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); const pipe = new ZodValidationPipe(schema);