Basic Architecture of Sport Web
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { Injectable, type PipeTransform } from '@nestjs/common';
|
||||
import type { ZodType } from 'zod';
|
||||
|
||||
import type { ApiFieldErrors } from '@sport/types';
|
||||
|
||||
import { AppException } from '../errors/app.exception';
|
||||
|
||||
/**
|
||||
* Validates a request payload with a Zod schema from @sport/validation and
|
||||
* returns the *parsed* value (with coercions and defaults applied).
|
||||
*
|
||||
* Chosen over class-validator so that exactly one schema governs both the API
|
||||
* and the frontend forms. See ADR-0006.
|
||||
*
|
||||
* @Post()
|
||||
* create(@Body(new ZodValidationPipe(createProductSchema)) body: CreateProductInput) {}
|
||||
*/
|
||||
@Injectable()
|
||||
export class ZodValidationPipe<TSchema extends ZodType> implements PipeTransform {
|
||||
constructor(private readonly schema: TSchema) {}
|
||||
|
||||
transform(value: unknown): unknown {
|
||||
const result = this.schema.safeParse(value);
|
||||
|
||||
if (!result.success) {
|
||||
throw AppException.validation(toFieldErrors(result.error.issues));
|
||||
}
|
||||
|
||||
return result.data;
|
||||
}
|
||||
}
|
||||
|
||||
interface ZodIssueLike {
|
||||
path: PropertyKey[];
|
||||
message: string;
|
||||
}
|
||||
|
||||
/** `items.0.quantity` → ["Must be at least 1"] — directly consumable by forms. */
|
||||
function toFieldErrors(issues: readonly ZodIssueLike[]): ApiFieldErrors {
|
||||
const fields: Record<string, string[]> = {};
|
||||
|
||||
for (const issue of issues) {
|
||||
const key = issue.path.map(String).join('.') || '_';
|
||||
(fields[key] ??= []).push(issue.message);
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
Reference in New Issue
Block a user