Skip to content

Instantly share code, notes, and snippets.

@bradley-pp
Last active April 11, 2025 15:54
Show Gist options
  • Select an option

  • Save bradley-pp/a97630266104fcf78aa79895149a1a04 to your computer and use it in GitHub Desktop.

Select an option

Save bradley-pp/a97630266104fcf78aa79895149a1a04 to your computer and use it in GitHub Desktop.
import { BadRequestException, PipeTransform } from '@nestjs/common';
import { ClassConstructor, plainToInstance } from 'class-transformer';
import { ValidationError, validate } from 'class-validator';
/**
* @example
* ```ts
* enum ContentType {
* IMAGE,
* VIDEO
* }
*
* interface BaseContentDto {
* type: ContentType;
* }
*
* class ImageContentDto implements BaseContentDto {
* @Equals(ContentType.IMAGE)
* type: ContentType.IMAGE,
*
* @IsString()
* caption: string;
* }
*
* class VideoContentDto implements BaseContentDto {
* @Equals(ContentType.VIDEO)
* type: ContentType.VIDEO,
*
* @IsString()
* length: number;
* }
*
* @Controller("content")
* class ContentController {
* constructor(
* private readonly contentService: ContentService
* ) {}
*
* @Post()
* async saveContent(
* @Body(
* new MultiDtoValidationPipe<BaseContentDto>(
* "type",
* [
* ImageContentDto,
* VideoContentDto
* ]
* )
* body: ImageContentDto | VideoContentDto
* )
* ) {
* return this.contentService.saveContent(body);
* }
* }
* ```
*/
export class MultiDtoValidationPipe<T extends object> implements PipeTransform {
constructor(
private readonly differentiator: keyof T,
private readonly dtos: ClassConstructor<T>[]
) {}
private hasDifferentiatorField(payload: unknown): payload is T & { [P in keyof T]: unknown } {
return Object.prototype.hasOwnProperty.call(payload, this.differentiator);
}
private getFormattedErrors(
errors: ValidationError[],
parent?: string
): string[] {
return errors
.map((error) => {
const messages: string[] = [];
if (error.constraints) {
messages.push(...Object.values(error.constraints));
}
if (error.children && error.children.length > 0) {
messages.push(
...this.getFormattedErrors(error.children, error.property)
);
}
return messages.map((msg) => `${parent ? `${parent}.` : ''}${msg}`);
})
.flat();
}
async transform(value: unknown) {
if (!this.hasDifferentiatorField(value)) {
throw new BadRequestException('Missing differentiator field.');
}
for (const dto of this.dtos) {
if (value[this.differentiator] !== new dto()[this.differentiator]) {
continue;
}
const instance = plainToInstance(dto, value);
const validationErrors = await validate(instance, {
whitelist: true,
forbidNonWhitelisted: true,
});
if (validationErrors.length === 0) {
return instance;
}
throw new BadRequestException(this.getFormattedErrors(validationErrors));
}
throw new BadRequestException(
'No valid DTO found based on differentiator field.'
);
}
}
import { ActionDTO } from './update.action.dto';
import {
BadRequestException,
PipeTransform,
} from '@nestjs/common';
import { ClassConstructor, plainToInstance } from 'class-transformer';
import { ValidationError, validate } from 'class-validator';
export class UpdateActionDtoValidationPipe implements PipeTransform {
constructor(private readonly dtos: ClassConstructor<ActionDTO>[]) {}
private hasTypeField(payload: unknown): payload is { type: unknown } {
return Object.prototype.hasOwnProperty.call(payload, 'type');
}
private getFormattedErrors(errors: ValidationError[], parent?: string): string[] {
return errors.map(error => {
const messages: string[] = [];
if (error.constraints) {
messages.push(...Object.values(error.constraints));
}
if (error.children && error.children.length > 0) {
messages.push(...this.getFormattedErrors(error.children, error.property));
}
return messages.map(msg => `${parent ? `${parent}.` : ''}${msg}`);
}).flat();
}
async transform(value: unknown) {
let foundValid = false;
if (!this.hasTypeField(value)) {
throw new BadRequestException();
}
for (const dto of this.dtos) {
if (foundValid || value.type !== new dto().type) {
continue;
}
foundValid = true;
const validationErrors = await validate(
plainToInstance(dto, value),
{
whitelist: true,
forbidNonWhitelisted: true
}
);
if (validationErrors.length === 0) {
return value;
}
throw new BadRequestException(this.getFormattedErrors(validationErrors));
}
throw new BadRequestException();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment