NestJs class-validator accepts empty payload - nestjs

I'm trying to create validator that accepts 2 values as strings (must exist, min/max length etc).
The issue I am facing is that when I POST empty payload the validation passes and TypeORM tries to insert null values and I end up with HTTP status 500.
When I POST with invalid payload the validation works properly.
I want to get proper validation errors as a response when payload is empty (existence of name property, min/max length etc...) ...
I tried adding various class annotations and global settings but no luck...
Global validation enabled:
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
forbidUnknownValues: true,
skipMissingProperties: false, //Thought this would check for missing properties
transform: true,
}),
);
Entity:
#Entity('r_cat')
export class ResearchCategory {
[...]
#PrimaryGeneratedColumn()
id: number;
#ApiProperty({
description: 'Name of the category',
example: 'Analytics, Integration',
})
#Column('text')
#IsString()
#ApiProperty()
#Length(2, 30)
#IsNotEmpty()
#IsDefined()
name: string;
[...]
My request object (DTO):
export class CreateResearchCategoryRequest extends PartialType(
OmitType(ResearchCategory, ['created_at', 'updated_at', 'deleted_at'] as const),
) {}
Controller:
#Post()
public async create(
#Req() req,
#Body() researchCategory: CreateResearchCategoryRequest,
): Promise<ResearchCategory> {
return await this.service.createNew(researchCategory, req.user);
}

I'm not sure this is the correct way to create a DTO based on the repository class. The usage of the mapper is different from what you've done since Nest examples show is all based on another DTO class, not a repository class.
However, I think the following snippet should work in your situation:
export class CreateResearchCategoryRequest extends OmitType(
ResearchCategory, ['created_at', 'updated_at', 'deleted_at'] as const
) {}

Related

NestJs/Swagger: How to add `additionalProperties: false` on an existing DTO class

Hello I am new to Nestjs and trying to implement additionalProperties: false on a DTO class that already has properties on it. I see that the additionalProperties key can be added inside #ApiProperty({ schema: ... { additionalProperties : false} }) but I want to add it like this:
class SomeResponseDto {
#ApiResponseProperty()
text: string;
#ApiResponseProperty()
id: string;
// maybe a new Decorator like this?
#ApiAdditionalProperties(false)
}
...so that only text and id is allowed in the SomeResponseDto. I want to avoid having to define every class as a schema object inside the controllers.
I should note that I'm using express-openapi-validator with nestjs/swagger, and do not want to use the class-validator/class-transformer plugins, so that I can validate responses as well as requests by using just nestjs/swagger decorators.
I have also tried this:
#ApiResponse({
status: 200,
description: 'success',
schema: {
oneOf: [
{
$ref: getSchemaPath(SomeResponseDto),
// additionalProperties: false, <-- this gets ignored
},
],
// additionalProperties: false, <-- this throws OpenApi invalid response errors
},
Is there any easy way to add additionalProperties: false on an existing DTO class?
Here is a workaround: Post this code inside the bootstrap() method of the application
const schemas = document?.components?.schemas;
Object.keys(schemas).forEach((item) => {
if (schemas[item]['properties']?.allowAdditional) {
schemas[item]['additionalProperties'] = true;
} else {
schemas[item]['additionalProperties'] = false;
}
});
This code above will set additionalProperties to false by default.
If for some reason you have a DTO class that you want to allow additionalProperties: true, then inside your DTO Class, add the following decorator and property:
export class SomeResponseDTO {
#ApiPropertyOptional()
allowAdditional?: boolean;
#ApiResponseProperty()
text: string;
#ApiResponseProperty()
id: string;
}
This is a simple solution for true/false case, but can be modified as needed to handle other use cases.
I hope this helps someone!

Nestjs custom class-validator decorator doesn't get the value from param

I have created a Custom ValidatorConstraint in Nestjs from class-validator, just to create my own decorator and apply later to DTO classes for validations.
Imagine this route.
foo/:client
after request it, I just want to check that client contains some pattern
client --> XXX123 ✘
client --> ZZZ123 ✔
I am struggling with it and although I saw some examples, it is still not very clear why it fails.
main.ts
app.useGlobalPipes(new ValidationPipe());
useContainer(app.select(AppModule), { fallbackOnErrors: true });
app.module.ts
providers: [..., IsValidClientConstraint],
app.controller.ts
#Get(':client')
getHello(#Param('client') client: ClientDTO): string {
custom.validator.ts
import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
import { Injectable } from '#nestjs/common';
#ValidatorConstraint({ async: false })
#Injectable()
export class IsValidClientConstraint implements ValidatorConstraintInterface {
validate(client: any, args: ValidationArguments) {
console.log(client)
return client.includes('ZZZ');
}
}
export function IsValidClient(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [],
validator: IsValidClientConstraint,
});
};
}
client.dto.ts
export class ClientDTO {
#IsValidClient({ message: 'blabla' })
client: string;
}
However doing a request with -> foo/XXX344
ERROR [ExceptionsHandler] Cannot read properties of undefined
So it is not receiving the value of the client itself
What am I missing there?
I with leave the repo here
https://github.com/ackuser/nestjs-sample-custom-validator
Thanks,
I appreciate any help
You don't have to pass parameter name to #Param decorator when you want to use class-validator to validate params, So change it to #Param() params: ClientDTO instead.
Use custom pipes if you want to validate each parameter one by one. because the DTO method you used turns all parameters (not just :client) into a single data class.
Also in IsValidClientConstraint check if client is defined before using it.

Perform a few custom async validations with class-validator

I would like to have the following dto:
export class SetEntryPasswordDto {
#ApiProperty()
#Validate(EntryBelongsToUser)
#Validate(EntryIsNotLocked)
#Type(() => Number)
#IsNumber()
id: number;
#ApiProperty()
#IsString()
#IsNotEmpty()
#Validate(PasswordMatchValidator)
#Matches(EValidator.PASSWORD, { message: 'password is not strong enough' })
password: string;
#ApiProperty()
#IsNotEmpty()
#IsString()
confirmPassword: string;
#ApiProperty()
#IsOptional()
#IsString()
passwordHint?: string;
#IsNumber()
userId: number;
}
The problem with it is that I need to do a couple of async validations and I would like to use the class-validator lib for this.
My question is: if I do this like in the code snippet above, can I be sure that the first one to complete is EntryIsNotLocked? If not then how to make those validation execute in order?
Thank you.
Additional information:
Seems like there's a bit of information that is of importance.
The EntryBelongsToUser and EntryIsNotLocked are the ValidatorConstraint classes. For instance, one of them looks as follows:
#ValidatorConstraint({ name: 'EntryIsNotLocked', async: false })
#Injectable()
export class EntryIsNotLocked implements ValidatorConstraintInterface {
constructor(
private readonly entryService: EntryService,
) {}
public async validate(val: any, args: ValidationArguments): Promise<boolean> {
// here goes some validation logic
}
public defaultMessage(args: ValidationArguments): string {
return `Unauthorized to execute this action`;
}
}
The second one looks exactly the same. So the question is can I guarantee the order by setting the async option of the ValidatorConstraint decorator to false for both of them?
No, you can't be sure of the sequential order of async functions. Thet's why you have validateSync method in class-validator package. You can use the validateSync method instead of the regular validate method to perform a simple non async validation.
See this for reference.

How to set default values for DTO in nest js?

I want to set default values for a node in a DTO. So if the value for that node is not passed, a default value will be used. Although this is working, I want the node should be present, the values is optional.
import { IsNotEmpty, IsDefined } from "class-validator";
export class IssueSearch
{
#IsDefined()
search: string;
#IsNotEmpty()
length: number = 10;
#IsNotEmpty()
lastId: string = "0"
}
This doesn't serve my purpose. I want the url to be searched like so
http://base-url/folder?search=value
If the value is not passed it should not throw an error.
But if the param search is not there it should throw an error.
If you want to set a default value go to entity and set in the field for example in mongo db
export class DumpDoc extends Document {
#Prop()
title: string;
#Prop({ default: new Date() }) //set as default
createdAt: string;
}

TypeORM - Update with OneToOne join attempts to delete related record

I have a project which uses TypeORM and I have the following two entities:
#Entity("Email")
export class EmailEntity extends BaseEntity implements IEmail {
#Column({length: 100}) to: string;
#Column({length: 100}) from: string;
#Column({length: 255}) subject: string;
#Column({nullable: true}) html: string;
#Column({nullable: true}) text: string;
}
and
#Entity("QueuedEmail")
export class QueuedEmailEntity extends BaseEntity implements IQueuedEmail {
#OneToOne(email => EmailEntity, {nullable: false, cascadeAll: true})
#JoinColumn()
email: EmailEntity;
#Column() retryCount: number;
#Column() status: QueuedEmailStatus;
constructor() {
super();
this.retryCount = 0;
this.status = QueuedEmailStatus.QueuedForDispatch;
}
}
BaseEntity is an abstract class which has an id column with the #PrimaryGeneratedColumn.
I have the following code which updates the status on a QueuedEmail:
const queuedEmailEntityArray: QueuedEmailEntity[] =
await this.queuedEmailRepository.createQueryBuilder("queuedEmail")
.where("queuedEmail.status = :queuedEmailStatus", {queuedEmailStatus: QueuedEmailStatus.QueuedForDispatch})
.innerJoinAndSelect("queuedEmail.email", "email")
.orderBy("queuedEmail.id", "DESC")
.getMany();
queuedEmailEntityArray.forEach(async (value, index) => {
let queuedEmailEntity: QueuedEmailEntity = queuedEmailEntityArray.pop();
queuedEmailEntity.status = QueuedEmailStatus.AttemptingToDispatch;
await this.queuedEmailRepository.persist(queuedEmailEntity);
});
Now, the select correctly loads the array or QueuedEmailEntity objects, the forEach runs but throws the following exception:
query failed: DELETE FROM "Email" WHERE "id"=$1 -- PARAMETERS: [1]
error during executing query:error: update or delete on table "Email" violates foreign key constraint "fk_d438362cf2adecbcc5b17f45606" on table "QueuedEmail"
The debug query output shows that TypeORM is updating the status field but also updating the emailId and then attempting to delete the EmailEntity record.
UPDATE "QueuedEmail" SET "status"=$1, "emailId"=$2 WHERE "id"=$3 -- PARAMETERS: [1,1,1]
DELETE FROM "Email" WHERE "id"=$1 -- PARAMETERS: [1]
I don't understand why TypeORM would need to update the emailId field when I have not changed the reference, much less why it would try to delete a record that is still referenced. Can anyone see what is wrong with the above code?

Resources