mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 22:16:26 +00:00
6c583c2f27
- One global ZodValidationPipe (APP_PIPE, createZodValidationPipe) validates
any @Body() typed with a createZodDto class; the custom exception factory
keeps TREK's { error: 'field: message; ...' } envelope byte-identical.
- The 7 piped controllers (collections, places, vacay, todo, health,
airtrail x2) now type bodies with colocated <domain>.dto.ts wrappers —
the Zod schemas in @trek/shared stay the single source of truth.
- OpenAPI: the hand-rolled api-zod.ts enricher is replaced by nestjs-zod's
cleanupOpenApiDoc; request bodies surface as $refs to named DTO schemas.
- validateBodyContracts() runs in buildApp() after init and refuses to boot
when any POST/PUT/PATCH @Body() (including @Body('field') sub-reads) lacks
a ZodDto metatype. The 175 pre-existing legacy handlers are grandfathered
in body-contract-allow-list.ts; the list is ratchet-only — the gate also
throws on stale entries, so migrating a domain forces their removal.
44 lines
1.7 KiB
TypeScript
44 lines
1.7 KiB
TypeScript
import { HttpException } from '@nestjs/common';
|
|
import { createZodValidationPipe } from 'nestjs-zod';
|
|
|
|
/**
|
|
* TREK's Zod validation pipe, built on nestjs-zod so it works in BOTH modes:
|
|
*
|
|
* - **Global** (`APP_PIPE` in AppModule): validates every parameter whose
|
|
* metatype is a `createZodDto(...)` class (the `<domain>.dto.ts` wrappers
|
|
* over the @trek/shared schemas) and passes everything else through
|
|
* untouched — legacy untyped bodies keep their exact behavior until their
|
|
* domain migrates. The boot gate in `validate-body-contracts.ts` is what
|
|
* makes forgetting a DTO fail closed.
|
|
* - **Per-parameter** (`@Body(new ZodValidationPipe(schema))`): still accepts
|
|
* an explicit schema/DTO for one-off cases.
|
|
*
|
|
* On failure it throws TREK's error envelope `{ error: string }` with status
|
|
* 400 — the same shape the legacy routes produce (`field: message; ...`,
|
|
* root-level issues labeled `body`), so the client's error handling and the
|
|
* pinned test assertions are unaffected by the nestjs-zod migration.
|
|
*/
|
|
interface ZodIssueLike {
|
|
path: (string | number | symbol)[];
|
|
message: string;
|
|
}
|
|
|
|
function isZodErrorLike(error: unknown): error is { issues: ZodIssueLike[] } {
|
|
return (
|
|
typeof error === 'object' && error !== null &&
|
|
Array.isArray((error as { issues?: unknown }).issues)
|
|
);
|
|
}
|
|
|
|
export const ZodValidationPipe = createZodValidationPipe({
|
|
createValidationException: (error: unknown): Error => {
|
|
if (isZodErrorLike(error)) {
|
|
const message = error.issues
|
|
.map((i) => `${i.path.map(String).join('.') || 'body'}: ${i.message}`)
|
|
.join('; ');
|
|
return new HttpException({ error: message }, 400);
|
|
}
|
|
return new HttpException({ error: 'Validation failed' }, 400);
|
|
},
|
|
});
|