@voznov/zod-dto-nestjs 0.3.0 → 0.3.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/README.md CHANGED
@@ -124,9 +124,33 @@ Method decorators that parse the return value of a controller (or any class) met
124
124
  - **`@ZodSerialize`** — runtime parsing only. Use on services, repositories, internal methods.
125
125
  - **`@ZodResponse`** — `@ZodSerialize` + auto-emit `@ApiResponse` Swagger metadata (and register inner DTOs via `@ApiExtraModels`). Use on controller routes.
126
126
 
127
- Both come in two overloads:
127
+ `ZodDtoSerializationError extends ZodDtoValidationError` wire up one exception filter to split client errors (400) from server bugs (500):
128
128
 
129
- - **Strict** — schema passed explicitly. The method's return type is constrained at compile time to match the schema's output; `tsc` errors on mismatch.
129
+ ```ts
130
+ import { ArgumentsHost, Catch, ExceptionFilter, HttpStatus } from '@nestjs/common';
131
+ import { ZodDtoValidationError } from '@voznov/zod-dto';
132
+ import { ZodDtoSerializationError } from '@voznov/zod-dto-nestjs';
133
+
134
+ @Catch(ZodDtoValidationError)
135
+ export class ZodExceptionFilter implements ExceptionFilter {
136
+ catch(error: ZodDtoValidationError, host: ArgumentsHost) {
137
+ const isServerBug = error instanceof ZodDtoSerializationError;
138
+ const status = isServerBug ? HttpStatus.INTERNAL_SERVER_ERROR : HttpStatus.BAD_REQUEST;
139
+ host.switchToHttp().getResponse().status(status).json({
140
+ statusCode: status,
141
+ message: error.message,
142
+ issues: isServerBug ? undefined : error.issues,
143
+ });
144
+ }
145
+ }
146
+
147
+ // main.ts
148
+ app.useGlobalFilters(new ZodExceptionFilter());
149
+ ```
150
+
151
+ Both decorators come in two overloads:
152
+
153
+ - **Strict** — schema passed explicitly. The method's return type is constrained at compile time to match the schema's output; `tsc` errors on mismatch as `TS1241: Unable to resolve signature of method decorator...` — the actual mismatch is on the deepest line of the message (`Type 'X' is not assignable to type 'NoteDto | Promise<NoteDto>'`).
130
154
  - **Loose** — no schema. Resolves from `design:returntype` metadata at runtime (`: NoteDto` annotation suffices). No compile-time check; doesn't work on generic return types (`NoteDto[]`, `Promise<NoteDto>`, unions) since TypeScript erases generics in metadata — pass the schema explicitly in that case.
131
155
 
132
156
  ```ts
@@ -184,24 +208,6 @@ Both decorators accept the full `ToDtoOptions` bag (`preprocessors`, `observers`
184
208
  async create(): Promise<NoteDto> { /* ... */ }
185
209
  ```
186
210
 
187
- ### Differentiating client errors from server errors
188
-
189
- `ZodDtoSerializationError extends ZodDtoValidationError`, so a single exception filter can split request-validation failures (client → 400) from response-validation failures (server → 500):
190
-
191
- ```ts
192
- import { ZodDtoValidationError } from '@voznov/zod-dto';
193
- import { ZodDtoSerializationError } from '@voznov/zod-dto-nestjs';
194
-
195
- @Catch(ZodDtoValidationError)
196
- export class ZodExceptionFilter implements ExceptionFilter {
197
- catch(error: ZodDtoValidationError, host: ArgumentsHost) {
198
- const isServerBug = error instanceof ZodDtoSerializationError;
199
- const status = isServerBug ? 500 : 400;
200
- // log, format, respond...
201
- }
202
- }
203
- ```
204
-
205
211
  ## API
206
212
 
207
213
  | Export | Description |
package/dist/index.cjs CHANGED
@@ -1388,10 +1388,19 @@ var wrapMethod = (schema, options, decoratorName) => (target, methodName, descri
1388
1388
  }[methodName];
1389
1389
  redecorateFromReflect(originalMethod, descriptor.value);
1390
1390
  };
1391
+ var API_RESPONSE_KEY = "swagger/apiResponse";
1391
1392
  var wrapApiResponse = (schema, options) => (target, propertyKey, descriptor) => {
1392
1393
  const resolved = resolveSchema(schema, target, propertyKey, "@ZodResponse");
1394
+ const status = options?.status ?? 200;
1395
+ const existing = descriptor.value ? Reflect.getMetadata(API_RESPONSE_KEY, descriptor.value) : void 0;
1396
+ if (existing?.[status]) {
1397
+ console.warn(
1398
+ `@ZodResponse on ${target.constructor.name}.${String(propertyKey)}: another response decorator already set metadata for status ${status} \u2014 they will silent-merge. Remove the duplicate to avoid mixed spec output.`
1399
+ );
1400
+ }
1393
1401
  const { so, innerSchemas } = applySwaggerDecorators(resolved);
1394
- (0, import_swagger2.ApiResponse)({ status: options?.status ?? 200, description: options?.description, schema: so })(target, propertyKey, descriptor);
1402
+ const schemaForApi = Object.keys(so).length === 1 && Array.isArray(so.oneOf) && so.oneOf.length === 1 ? so.oneOf[0] : so;
1403
+ (0, import_swagger2.ApiResponse)({ status, description: options?.description, schema: schemaForApi })(target, propertyKey, descriptor);
1395
1404
  if (innerSchemas.size > 0) {
1396
1405
  const classTarget = typeof target === "function" ? target : target.constructor;
1397
1406
  (0, import_swagger2.ApiExtraModels)(...innerSchemas)(classTarget);